C/C++ YUV 文件叠加自定义符号

241 阅读10分钟

一、前言

需要在图片文件上叠加文字,但是要在4M内存开发板上实现,实际内存不足1M,怎么实现?这个问题在网上查找的解决方案都需要使用第三方库文件,下载文字图像库,但是此开发板不能承受住这么大的内存,所以只能另辟蹊径,将图片文件还原为YUV文件,然后在YUV文件中计算分量坐标完成叠加文字的功能,这里就可以将需要的文字以十六进制数组的形式放在头文件中定义编译进程序,来解决内存不足的问题。然后针对YUV文件叠加文字这个问题查找,发现了这篇文章: 最原始的yuv图像叠加文字的实现--手动操作像素_yuv图片如何显示中文-CSDN博客。 这篇文章写了YUV文件叠加文字的实现算法,但是这里的图案只有数字以及两个符号,如果需要扩展的话,需要自己添加。然后我就找生成点阵的软件,结果没有找到,只有LED屏点阵数组生成,一个是十六进制数组,一个是二进制点阵。那现在如果想要扩展两个选择:一是自己写程序来扩展图案数组;二是改变算法,使它可以使用二进制点阵。二是修改文字中的代码,使它可以使用二进制点阵。我选择第二种方式,因为它更简单,并且使内存占用更小。

二、YUV分量计算头文件

根据个人需求修改后的头文件,digital_led.h

#ifndef _DIGITAL_LED_H__
#define _DIGITAL_LED_H__

#define BACKGROUND_BLACK 0  //白字,黑色背景
#define BACKGROUND_TRANSPARENT 1  //白字,透明背景
#define BACKGROUND_NULL 2  //半透明,无背景

#define COLOR_YUV420p 0
#define COLOR_NV12 1 //420SP,Y平面,UV非平面:YYYYYYYYUVUV --- 没有经过测试,不知道是否存在问题
#define COLOR_YUV422p 2
#define COLOR_YUV444p 3
#define COLOR_Yp 9 //Y平面模型都小于这个值

#define TAB_START_CHAR ' ' //字库起始字符
#define TAB_END_CHAR '~'   //字库结束字符

static int digital_width = 16;
static int digital_height = 16;
static int gap_width = 0;

//这里直接设置为黑背景
int overOSD(int colorformat, unsigned char *yuvOut, const unsigned char *osd, int osdW, int osdH, int yuvW, int yuvH , int x, int y);
//白字,透明背景 
int overOSD1(int colorformat, unsigned char *yuvOut, const unsigned char *osd, int osdW, int osdH, int yuvW, int yuvH , int x, int y);
//直接将Y分量取反,不改变UV分量,达到字体半透明的效果
int overOSD2(int colorformat, unsigned char *yuvOut, const unsigned char *osd, int osdW, int osdH, int yuvW, int yuvH , int x, int y);
//多字符写入
void overinfo(char*info, int colorformat, int overtype, unsigned char *yuvOut ,int yuvW, int yuvH , int x, int y);

static const unsigned char tilde_sign[32] = {//~
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00111110, 0b00110000, 
    0b01110111, 0b01110000, 
    0b01110011, 0b11110000, 
    0b01110001, 0b11100000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000, 
    0b00000000, 0b00000000
};

static const unsigned char* DigitalArray[] = {
    &space_sign[0], &exclamation_sign[0], &double_quotation[0], &pound_sign[0], &dollar_sign[0], &percent_sign[0], &ampersand_sign[0], &single_quotation[0], &left_bracket[0], &right_bracket[0],
    &asterisk_sign[0], &plus_sign[0], &omma_sign[0], &mimus_sign[0], &point_sign[0], &diagnal_bar[0], &digital_0[0], &digital_1[0], &digital_2[0], &digital_3[0],
    &digital_4[0], &digital_5[0], &digital_6[0], &digital_7[0], &digital_8[0], &digital_9[0], &colon_sign[0], &semico_sign[0], &less_than[0], &equal_than[0],
    &greater_than[0], &question_mark[0], &aite_symbol[0], &caplital_letter_A[0], &caplital_letter_B[0], &caplital_letter_C[0], &caplital_letter_D[0], &caplital_letter_E[0], &caplital_letter_F[0], &caplital_letter_G[0],
    &caplital_letter_H[0], &caplital_letter_I[0], &caplital_letter_J[0], &caplital_letter_K[0], &caplital_letter_L[0], &caplital_letter_M[0], &caplital_letter_N[0], &caplital_letter_O[0], &caplital_letter_P[0], &caplital_letter_Q[0],
    &caplital_letter_R[0], &caplital_letter_S[0], &caplital_letter_T[0], &caplital_letter_U[0], &caplital_letter_V[0], &caplital_letter_W[0], &caplital_letter_X[0], &caplital_letter_Y[0], &caplital_letter_Z[0], &square_brackets_l[0],
    &backslash_sign[0], &square_brackets_r[0], &power_sign[0], &bash_sign[0], &inverted_quotation[0], &small_letter_a[0], &small_letter_b[0], &small_letter_c[0], &small_letter_d[0], &small_letter_e[0],
    &small_letter_f[0], &small_letter_g[0], &small_letter_h[0], &small_letter_i[0], &small_letter_j[0], &small_letter_k[0], &small_letter_l[0], &small_letter_m[0], &small_letter_n[0], &small_letter_o[0],
    &small_letter_p[0], &small_letter_q[0], &small_letter_r[0], &small_letter_s[0], &small_letter_t[0], &small_letter_u[0], &small_letter_v[0], &small_letter_w[0], &small_letter_x[0], &small_letter_y[0],
    &small_letter_z[0], &left_brace[0], &or_sign[0], &right_brace[0], &tilde_sign[0]
    };

#endif

三、YUV分量计算的源文件

根据个人需求修改后的源文件,digital_led.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "digital_led.h"

//这里直接设置为黑背景
int overOSD(int colorformat,unsigned char *yuvOut , const unsigned char *osd, int osdW, int osdH, int yuvW, int yuvH , int x, int y)
{
    if((x + osdW) > yuvW || (y + osdH) > yuvH){
        printf("pram err\n");
        return 0;
    }
    if(yuvW%2 != 0 || yuvH%2 != 0){
        printf("[%s%d]pram err\n",__FUNCTION__,__LINE__ );
        return 0;
    }    
 
    if(colorformat == COLOR_NV12){//NV12
        //y:
        int i=0,j=0;
        for (i = 0; i < osdH * osdW / 8; i++)
        {
            unsigned char osddata = osd[i];
            for (j = 0; j < 8; j++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = 0xff;
                }else{
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = 0x00;
                }
            }
        }
        
        //uv 这里的坐标计算尽量保持原本的意思,看起来比较冗余,可以简化提供效率
        yuvOut+=yuvW*yuvH;
        for(i=0;i<osdH/2;i++){
            for(j=0;j<osdW;j++){
                yuvOut[yuvW *(i+y/2) + j+x] = 0x80;
            }
        }
    }else if(colorformat == COLOR_YUV420p){//yuv420p
        //y: 这里的坐标计算尽量保持原本的意思,看起来比较冗余,可以简化提供效率
        int i = 0, j = 0;
        for (i = 0; i < osdH * osdW / 8; i++)
        {
            unsigned char osddata = osd[i];
            for (j = 0; j < 8; j++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = 0xff;
                }else{
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = 0x00;
                }
            }
        }
        
        //u 
        //这里的坐标计算尽量保持原本的意思,看起来比较冗余,可以简化提供效率
        //需要保证w和h是偶数
        yuvOut+=yuvW*yuvH;
        for(i=0;i<osdH/2;i++){
            for(j=0;j<osdW/2;j++){
                yuvOut[yuvW/2 *(i+y/2) + j+x/2] = 0x80;
            }
        }
        //v 这里的坐标计算尽量保持原本的意思,看起来比较冗余,可以简化提供效率
        yuvOut+=yuvW*yuvH/4;
        for(i=0;i<osdH/2;i++){
            for(j=0;j<osdW/2;j++){
                yuvOut[yuvW/2 *(i+y/2) + j+x/2] = 0x80;
            }
        }
    }
    return 1;
}
 
//白字,透明背景 
int overOSD1(int colorformat,unsigned char *yuvOut , const unsigned char *osd, int osdW, int osdH, int yuvW, int yuvH , int x, int y)
{
    if((x + osdW) > yuvW || (y + osdH) > yuvH){
        printf("pram err\n");
        return 0;
    }
    if(colorformat == COLOR_NV12){//NV12
        //y:
        int i=0,j=0;
        for (i = 0; i < osdH * osdW / 8; i++)
        {
            unsigned char osddata = osd[i];
            for (j = 0; j < 8; j++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = 0xff;
                }else{
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = 0x00;
                }
            }
        }
 
        //uv 这里的坐标计算尽量保持原本的意思,看起来比较冗余,可以简化提供效率
        yuvOut += yuvW * yuvH;
        for (i = 0; i < osdH * osdW / 8; i++)//
        {
            unsigned char osddata = osd[i];
            for (j = 0; j < 8; j++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    int index_y = yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x);
                    yuvOut[(index_y / yuvW) / 2 * (yuvW / 2) * 2 + (index_y % yuvW)] = 0x80;//u
                    yuvOut[(index_y / yuvW) / 2 * (yuvW / 2) * 2 + (index_y % yuvW) + 1] = 0x80;//v
                }
            }
        }
    }else if(colorformat == COLOR_YUV420p){//YUV420p
        //y:
        int i=0,j=0;
        for (i = 0; i < osdH * osdW / 8; i++)
        {
            unsigned char osddata = osd[i];
            for (j = 0; j < 8; j++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = 0xff;
                }
            }
        }
 
        //u 这里的坐标计算尽量保持原本的意思,看起来比较冗余,可以简化提供效率
        yuvOut += yuvW * yuvH;
        for (i = 0; i < osdH * osdW / 8; i ++)
        {
            unsigned char osddata = osd[i];
            for (j = 1; j < 8; j ++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    int index_y = yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x);
                    if((index_y / yuvW) % 2 == 0)
                        yuvOut[(index_y / yuvW) / 2 * (yuvW / 2) + (index_y % yuvW) / 2] = 0x80;
                }
            }
        }

        //v 这里的坐标计算尽量保持原本的意思,看起来比较冗余,可以简化提供效率
        yuvOut += yuvW * yuvH / 4;
        for (i = 0; i < osdH * osdW / 8; i ++)
        {
            unsigned char osddata = osd[i];
            for (j = 1; j < 8; j ++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    int index_y = yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x);
                    if((index_y / yuvW) % 2 == 0)
                        yuvOut[(index_y / yuvW) / 2 * (yuvW / 2) + (index_y % yuvW) / 2] = 0x80;
                }
            }
        }
    }
    return 1;
}

//直接将Y分量取反,不改变UV分量
int overOSD2(int colorformat, unsigned char *yuvOut, const unsigned char *osd, int osdW, int osdH, int yuvW, int yuvH , int x, int y)
{
    if((x + osdW) > yuvW || (y + osdH) > yuvH){
        printf("pram err\n");
        return 0;
    }

    if(colorformat <= COLOR_Yp){
        //y
        int i = 0, j = 0;
        for (i = 0; i < osdH * osdW / 8; i++)
        {
            unsigned char osddata = osd[i];
            for (j = 0; j < 8; j++)
            {
                if(((osddata << j) & 0x80) == 0x80){
                    yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)] = ~yuvOut[yuvW * (i / (osdW / 8) + y) + (j + (i%(osdW/8)) * 8 + x)];
                }
            }
        }
    }

    return 1;
}

//多字符写入
void overinfo(char*info,int colorformat,int overtype,unsigned char *yuvOut ,int yuvW, int yuvH , int x, int y)
{
    int infolen = strlen(info);
    printf("infolen = %d\n", infolen);
    int i = 0;
    int postionX = x;
    int postionY = y;
    for(i = 0; i < infolen; i++){
        if(info[i] == '\n'){
            postionX = x;
            postionY += digital_height;
        }else if(TAB_START_CHAR <= info[i] && info[i] <= TAB_END_CHAR){
            int ret = 1;
            if(overtype == BACKGROUND_BLACK){
                ret = overOSD(colorformat, yuvOut, DigitalArray[info[i] - TAB_START_CHAR], digital_width, digital_height, yuvW, yuvH, postionX, postionY);
            }else if(overtype == BACKGROUND_TRANSPARENT){
                ret = overOSD1(colorformat, yuvOut, DigitalArray[info[i] - TAB_START_CHAR], digital_width, digital_height, yuvW, yuvH, postionX, postionY);
            }else if(overtype == BACKGROUND_NULL){
                ret = overOSD2(colorformat, yuvOut, DigitalArray[info[i] - TAB_START_CHAR], digital_width, digital_height, yuvW, yuvH, postionX, postionY);
            }else{
                printf("error: overtype unknown definition %d\n", overtype);
                return;
            }
            if(ret == 0){//坐标超过,换行写入
                postionX = x;
                postionY += digital_height;
                i --;
            }else if(ret == 1){
                postionX += digital_width;
            }else{//未知返回
                printf("error: unknown return %d\n", ret);
            }

            if((postionY + digital_height) > yuvH){
                printf("stop drawing at \'%c\'! postionY is greter than yuvH!\n", info[i]);
                return;
            }
        }else{
            printf("an unknown symbol has been skipped: %c\n", info[i]);
        }
    }
}

四、测试说明

看代码,在多字符写入的函数中有判断换行符和溢出,调用测试:

overinfo("1\n234567890\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$^*(){}[] :;%\\|/><.,`qweroiugsdmnxcvzvjsqodasjdfaoiugfkajhOWEYRAHDFAYEWFAJDSLAKWEORYFASDFJALKSDFWQOURTYASGDF", COLOR_YUV420p, BACKGROUND_NULL, buf, w, h, x + 10, y + (digital_height * 2));

原图片和处理后的图片:

image.png

image.png

五、字符图案扩展

这里我使用的是在线软件,地址如下:点阵生成软件|字模提取(支持中文) - LED、OLED、LCD、单片机在线取模、中文点阵取模软件、在线显示屏取模 - Arduino|STM32|STM8 - 文字或图片点阵生成软件 (zhetao.com)