一、 概述
将RGB图片打在YUV上需要注意的是, 字体之外应该透明, 否则背景也会被覆盖不好看, 所以RGB必须有透明度, 本测试格式为BMP ARGB8888(也即B是最低字节, A是最高字节),
YUV格式为NV21(YUV420SP), 第一步是将ARGB转换成NV21, 然后小NV21打在打NV21上即可, 最后保存图片, 用7yuv.exe软件查看!
二、 示例代码
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/ioctl.h> #include <stdlib.h> #include <linux/types.h> #include <linux/videodev2.h> #include <malloc.h> #include <math.h> #include <string.h> #include <sys/mman.h> #include <errno.h> #include <assert.h> #define MAX_ICON_NUM 42 #define SAVE_IMG_PATH "./save_1280x720_nv21.yuv" #define WATERMARK_BMP_DIR "./watermark" struct wm_icon { int id; int width; int height; unsigned char *y; unsigned char *c; unsigned char *alph; }; struct wm_icon icon_arry[MAX_ICON_NUM]; struct disp_content{ int x; int y; #define MAX_CONTENT_LEN 32 char content[MAX_CONTENT_LEN]; }; struct backgroud_info { int width; int height; unsigned char *y; unsigned char *c; }; static void argb8888toyuv420sp(unsigned char *src_p, int width, int height, unsigned char *dest_y, unsigned char *dest_c, unsigned char *dest_alph) { int i,j; for(i = 0; i < (int)height; i++) { if((i&1) == 0) { for(j= 0; j< (int)width; j++) { *dest_y = (299*src_p[2]+587*src_p[1]+114*src_p[0])/1000; if((j&1) == 0) *dest_c++ = 128+(564*(src_p[0]-*dest_y)/1000); //cb else *dest_c++ = 128+(713*(src_p[2]-*dest_y)/1000); //cr *dest_alph++ = src_p[3]; src_p +=4; dest_y++; } } else { for(j= 0; j< (int)width; j++) { *dest_y = (299*src_p[2]+587*src_p[1]+114*src_p[0])/1000; *dest_alph++ = src_p[3]; src_p +=4; dest_y++; } } } return; } int init_watermark() { FILE *fp = NULL; int i; int pic_width = 0, pic_height = 0; char filename[256]; unsigned char *argb_buf = NULL; for(i = 0; i < MAX_ICON_NUM; i++) { sprintf(filename, "%s/argb8888_24x32_%d.bmp", WATERMARK_BMP_DIR, i); printf("open file %s ", filename); fp = fopen(filename, "r"); if(NULL == fp) { printf("Fail to open file %s(%s)!", filename, strerror(errno)); goto OPEN_FILE_ERR; } icon_arry[i].id = i; /* BMP格式解析说明: https://www.cnblogs.com/vedic/p/13608566.html */ fseek(fp, 18, SEEK_SET); fread(&pic_width, 1, 4, fp); fread(&pic_height, 1, 4, fp); icon_arry[i].width = abs(pic_width); icon_arry[i].height = abs(pic_height); printf("real width=%d(%d), height=%d(%d) ", icon_arry[i].width, pic_width, icon_arry[i].height, pic_height); icon_arry[i].y = (unsigned char*)malloc(icon_arry[i].width * icon_arry[i].height*5/2); if(NULL == icon_arry[i].y) { printf("malloc picture yuv fail ! "); fclose(fp); goto ALLOC_Y_ERR; } memset(icon_arry[i].y, 0xff, (icon_arry[i].width * icon_arry[i].height*5/2)); icon_arry[i].alph = icon_arry[i].y + icon_arry[i].width * icon_arry[i].height; icon_arry[i].c = icon_arry[i].alph + icon_arry[i].width * icon_arry[i].height; argb_buf = (unsigned char *)malloc(icon_arry[i].width * icon_arry[i].height * 4); if (NULL == argb_buf) { printf("malloc bmp buf fail ! "); fclose(fp); goto ALLOC_BUF_ERR; } fseek(fp, 54, SEEK_SET); fread(argb_buf, icon_arry[i].width * icon_arry[i].height * 4, 1, fp); argb8888toyuv420sp(argb_buf, icon_arry[i].width, icon_arry[i].height, icon_arry[i].y, icon_arry[i].c, icon_arry[i].alph); fclose(fp); free(argb_buf); } return 0; ALLOC_BUF_ERR: ALLOC_Y_ERR: OPEN_FILE_ERR: for(i = 0; i < MAX_ICON_NUM; ++i) { if(icon_arry[i].y != NULL) { free(icon_arry[i].y); icon_arry[i].y = NULL; } } return -1; } static int get_word_num(char val) { int i; int cnt = 0; for(i=7; i>=0; i--) { if(!(val & (1<<i))) break; else cnt++; } if (cnt == 0) return 1; return cnt; } #define DEFAULT_NUM 36 static int get_word_index(char *data, int wordlen) { int index = DEFAULT_NUM; if(wordlen == 1) { if (*data >= '0' && *data <= '9') { printf("choose numben 0 ~ 9 "); index = (*data - 48); //落在数组下标0开始 } else if (*data >= 'A' && *data <= 'Z') { printf("choose A~Z "); index = (*data - 55); //落在数组下标10开始 } else if (*data == ' ') { printf("choose kong "); index = 36; } else if (*data == '-') { printf("choose - "); index = 37; } else if (*data == '.') { printf("choose . "); index = DEFAULT_NUM; } else if (*data == ':') { printf("choose eng : "); index = 38; } else if (*data == '/') { printf("choose / "); index = 39; } else { printf("not match %s , use DEFAULT_NUM ", data); index = DEFAULT_NUM; } } else if (wordlen == 3) { if(!memcmp(data, "京", 3)) { printf("choose %s ", "京"); index = 40; } else if (!memcmp(data, "沪", 3)) { printf("choose %s ", "沪"); index = 41; } else if (!memcmp(data, ":", 3)) { printf("choose chn : "); index = 38; } else { printf("choose DEFAULT_NUM "); index = DEFAULT_NUM; } } printf(" wordlen -> %d, index -> %d ", wordlen, index); return index; } void yuv420sp_blending(unsigned char *bg_y, unsigned char *bg_c, int bg_width, int bg_height, int left, unsigned int top, int fg_width, int fg_height, unsigned char *fg_y, unsigned char *fg_c, unsigned char *alph) { unsigned char *bg_y_p = NULL; unsigned char *bg_c_p = NULL; int i = 0; int j = 0; bg_y_p = bg_y + top * bg_width + left; bg_c_p = bg_c + (top >>1)*bg_width + left; for(i = 0; i<(int)fg_height; i++) { if((i&1) == 0) { for(j=0; j< (int)fg_width; j++) { *bg_y_p = ((256 - *alph)*(*bg_y_p) + (*fg_y++)*(*alph))>>8; *bg_c_p = ((256 - *alph)*(*bg_c_p) + (*fg_c++)*(*alph))>>8; alph++; bg_y_p++; bg_c_p++; } bg_c_p = bg_c_p + bg_width - fg_width; } else { for(j=0; j< (int)fg_width; j++) { *bg_y_p = ((256 - *alph)*(*bg_y_p) + (*fg_y++)*(*alph))>>8; alph++; bg_y_p++; } } bg_y_p = bg_y_p + bg_width - fg_width; } } int save_image(void *addr, int length) { FILE *fp = fopen(SAVE_IMG_PATH, "w"); if(fp == NULL) { printf("Fail to fopen %s ", SAVE_IMG_PATH); exit(-1); } fwrite(addr, length, 1, fp); fflush(fp); usleep(500); fclose(fp); return 0; } int push_wm2bg(unsigned char *bg_yuv_buf, int bg_width, int bg_height, struct disp_content *single_wm) { int pos_x; struct backgroud_info bg_info; int buflen, wordlen; char *p_content; int pic_index; int i; bg_info.width = bg_width; bg_info.height = bg_height; bg_info.y = bg_yuv_buf; bg_info.c = bg_yuv_buf + bg_width * bg_height; p_content = single_wm->content; buflen = strlen(single_wm->content); pos_x = single_wm->x; printf(" show: %s(%d Bytes): ", p_content, buflen); for (i = 0; i < strlen(single_wm->content); i++) { wordlen = get_word_num(p_content[0]); buflen -= wordlen; if(buflen < 0) break; pic_index = get_word_index(p_content, wordlen); yuv420sp_blending(bg_info.y, bg_info.c, bg_info.width, bg_info.height, pos_x, single_wm->y, icon_arry[pic_index].width, icon_arry[pic_index].height, icon_arry[pic_index].y, icon_arry[pic_index].c, icon_arry[pic_index].alph); pos_x += icon_arry[pic_index].width; p_content += wordlen; } return 0; } /* Note: * input file 1280*720 NV21 format * watermark ARGB8888 */ int main(int argc, char **argv) { unsigned int bg_width = 1280; unsigned int bg_height = 720; unsigned int bg_nv21_size = bg_width * bg_height * 3/2; FILE *fp; char *bg_file = argv[1]; unsigned char *bg_yuv_buf; /* 汉字编码用UTF-8, 建议用UTF-8编码 */ struct disp_content single_wm = {200, 200, "0 123456789ABC京沪:-/"}; if(bg_file == NULL) { printf("Pls input backgroud file ! "); printf(" eg: %s 1280x720_nv21.yuv ", argv[0]); exit(-1); } fp = fopen(bg_file, "rb"); if(fp == NULL) { printf("open backgroud file(%s) fail ! ", bg_file); exit(-1); } bg_yuv_buf = (unsigned char *)malloc(bg_nv21_size); if(bg_yuv_buf == NULL){ printf("malloc bg_yuv_buf fail! "); exit(-1); } fread(bg_yuv_buf, 1, bg_nv21_size, fp); init_watermark(); push_wm2bg(bg_yuv_buf, bg_width, bg_height, &single_wm); save_image(bg_yuv_buf, bg_nv21_size); return 0; }
fzk@mdvr:push_wm2yuv$ ./a.out 1280x720_nv21.yuv open file ./watermark/argb8888_24x32_0.bmp real width=32(32), height=48(48) open file ./watermark/argb8888_24x32_1.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_2.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_3.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_4.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_5.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_6.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_7.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_8.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_9.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_10.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_11.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_12.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_13.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_14.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_15.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_16.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_17.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_18.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_19.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_20.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_21.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_22.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_23.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_24.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_25.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_26.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_27.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_28.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_29.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_30.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_31.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_32.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_33.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_34.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_35.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_36.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_37.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_38.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_39.bmp real width=24(24), height=32(-32) open file ./watermark/argb8888_24x32_40.bmp real width=24(24), height=32(32) open file ./watermark/argb8888_24x32_41.bmp real width=24(24), height=32(32) show: 0 123456789ABC京沪:-/(25 Bytes): choose numben 0 ~ 9 wordlen -> 1, index -> 0 choose kong wordlen -> 1, index -> 36 choose numben 0 ~ 9 wordlen -> 1, index -> 1 choose numben 0 ~ 9 wordlen -> 1, index -> 2 choose numben 0 ~ 9 wordlen -> 1, index -> 3 choose numben 0 ~ 9 wordlen -> 1, index -> 4 choose numben 0 ~ 9 wordlen -> 1, index -> 5 choose numben 0 ~ 9 wordlen -> 1, index -> 6 choose numben 0 ~ 9 wordlen -> 1, index -> 7 choose numben 0 ~ 9 wordlen -> 1, index -> 8 choose numben 0 ~ 9 wordlen -> 1, index -> 9 choose A~Z wordlen -> 1, index -> 10 choose A~Z wordlen -> 1, index -> 11 choose A~Z wordlen -> 1, index -> 12 choose 京 wordlen -> 3, index -> 40 choose 沪 wordlen -> 3, index -> 41 choose chn : wordlen -> 3, index -> 38 choose - wordlen -> 1, index -> 37 choose / wordlen -> 1, index -> 39
三、注意事项
1. BMP具体解析请查看上一篇博客: https://www.cnblogs.com/vedic/p/13608566.html
2. 请用UTF-8格式查看, 否则中文会乱码
3. 代码自动解析BMP头部图片的宽高, 所以不只是支持32x24
4. 黑白图片没问题, 彩色会有问题
5. 测试用的背景图片: https://files-cdn.cnblogs.com/files/vedic/1280x720_nv21.bmp 注意将后缀改成.yuv
6. 测试用的水印图标: https://files.cnblogs.com/files/vedic/watermark.rar
7. 最后展示: