Bresenham的线算法以Jack Elton Bresenham命名,他于1962年在IBM开发。是计算机图形学中的经典算法,恰好最近公司项目中需要实现类似的功能,于是就实践了一把,为了以后爬坑方便,记录在此。
图形转YUV
我们是在YUV裸数据上去绘,而通常的图像都是代容器格式的JPG或者PNG等等,所以需要首先将图片转换为YUV裸数据,我们用FFMPEG这款视频处理领域的瑞士军刀完成此功能。
常用的YUV格式有NV12和NV21两种,,NV12 分两个平面,Y 平面和 UV 平面存储,UV 在同一个平面交叉存储,所以也被称为interleaved,NV12格式也叫IOS模式,同FFMPEG 中的YUV420SP,Y和UV分成两个部分分别存储,NV21格式是Android上的常用格式,格式类似于NV12,只是U和V的顺序相反。
转换命令:
ffmpeg -i car.jpeg -pix_fmt nv12 car.nv12.yuv
原图:
命令不会改变图像的分辨率符合,原图分辨率多少,转换出的YUV分辨率也是多少,如果你需要作SCALE,则事先在原来的图像格式上作SCALE再做YUV转换即可,或者YUV转换过程中通过-s选项也可以达到目的。
ffmpeg -i car.jpeg -pix_fmt nv12 -s 1280x720 scale.nv12.yuv
转换后的YUV用如下命令查看:
ffplay -pix_fmt nv12 -f rawvideo -video_size 3840x2160 ./car.nv12.yuv
如果错把NV12写成了NV21,则图形颜色会发生变化,见下图:
并且文件大小应该是:3840x2160*3/2 = 12441600,和实际值相符。
YUView分析:
放大后看细节每四个Y对应1个U和1个V.
画线
布雷森汉姆算法的原理如分析:
【附源码】布雷森汉姆直线算法(bresenham‘s line algorithm)_布雷森汉姆算法-CSDN博客
上图中根据Bresenham算法得到的轨迹如红色折线所示:
YUV文件已经生成,现在我们可以用Bresenham算法写程序在上面画线了,比如我们绘制从[0,0]开始到[3840,2160]的对角线,程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define DBG(fmt, ...) do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0)
static int yuv_width, yuv_height;
static unsigned char *yuvbuf = NULL;
void setpixel(int x,int y)
{
if(x < 0 || x >= yuv_width)
{
DBG("x is not valied %d.", x);
return;
}
if(y < 0 || y >= yuv_height)
{
DBG("y is not valied %d.", y);
return;
}
// green yuv 0x96, 0x2c, 0x15
// red yuv 0x4c, 0x55, 0xff
yuvbuf[y * yuv_width + x] = 0x96;
yuvbuf[yuv_height*yuv_width + (y/2) * yuv_width + x/2 * 2] = 0x2c;
yuvbuf[yuv_height*yuv_width + (y/2) * yuv_width + x/2 * 2 + 1] = 0x15;
return;
}
void draw_line(int x0, int y0, int x1, int y1)
{
int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
int dy = -abs(y1-y0), sy = y0<y1 ? 1 : -1;
// error value e_xy
int err = dx + dy, e2;
for( ; ; )
{
setpixel(x0,y0);
if (x0 == x1 && y0 == y1)
{
break;
}
e2 = 2 * err;
// e_xy+e_x > 0
// e_xy+e_y < 0
if (e2 >= dy)
{
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
}
return;
}
int main(int argc, char **argv)
{
FILE *file, *filewrite;
int width = atoi(argv[1]);
int height = atoi(argv[2]);
yuv_width = width;
yuv_height = height;
int size = width * height * 3 / 2;
yuvbuf = malloc(size);
if(yuvbuf == NULL)
{
DBG("malloc yuvbuf failure.");
return -1;
}
memset(yuvbuf, 0x00, size);
file = fopen(argv[3], "rb");
if(file == NULL)
{
DBG("fatal error, open file %s failure, please check the file status.", argv[3]);
return -1;
}
filewrite = fopen("lined.yuv", "wb+");
if(filewrite == NULL)
{
DBG("fatal error, open file lined.yuv failure, please check the file status.");
return -1;
}
fseek(file, 0, SEEK_END);
int filelen = ftell(file);
DBG("file %s len %d byets.", argv[3], filelen);
if(filelen != size)
{
DBG("yuvdata has been corrupted.size %d", size);
/*return -1;*/
}
fseek(file, 0, SEEK_SET);
if(fread(yuvbuf, 1, filelen, file) != filelen)
{
DBG("read file failure, size wrong.");
return -1;
}
draw_line(0, 0, width-1, height-1);
fseek(filewrite, 0, SEEK_SET);
if(fwrite(yuvbuf, 1, filelen, filewrite) != filelen)
{
DBG("write file failure.");
return -1;
}
fflush(filewrite);
fsync(fileno(filewrite));
fclose(file);
fclose(filewrite);
return 0;
}
ffplay -pix_fmt nv12 -f rawvideo -video_size 3840x2160 ./lined.yuv
可以看到一条绿色的对角线:
颜色修改为红色:
绘制巴塞尔曲线:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <assert.h>
#define DBG(fmt, ...) do { printf("%s line %d, "fmt"\n", __func__, __LINE__, ##__VA_ARGS__); } while (0)
static int yuv_width, yuv_height;
static unsigned char *yuvbuf = NULL;
void setpixel(int x,int y)
{
if(x < 0 || x >= yuv_width)
{
DBG("x is not valied %d.", x);
return;
}
if(y < 0 || y >= yuv_height)
{
DBG("y is not valied %d.", y);
return;
}
// green yuv 0x96, 0x2c, 0x15
// red yuv 0x4c, 0x55, 0xff
yuvbuf[y * yuv_width + x] = 0x96;
yuvbuf[yuv_height*yuv_width + (y/2) * yuv_width + x/2 * 2] = 0x2c;
yuvbuf[yuv_height*yuv_width + (y/2) * yuv_width + x/2 * 2 + 1] = 0x15;
return;
}
void draw_line(int x0, int y0, int x1, int y1)
{
int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
int dy = -abs(y1-y0), sy = y0<y1 ? 1 : -1;
// error value e_xy
int err = dx + dy, e2;
for( ; ; )
{
setpixel(x0,y0);
if (x0 == x1 && y0 == y1)
{
break;
}
e2 = 2 * err;
// e_xy+e_x > 0
// e_xy+e_y < 0
if (e2 >= dy)
{
err += dy;
x0 += sx;
}
if (e2 <= dx)
{
err += dx;
y0 += sy;
}
}
return;
}
void plot_quad_bezierseg(int x0, int y0, int x1, int y1, int x2, int y2)
{
int sx = x2-x1, sy = y2-y1;
long xx = x0-x1, yy = y0-y1, xy; /* relative values for checks */
double dx, dy, err, cur = xx*sy-yy*sx; /* curvature */
assert(xx*sx <= 0 && yy*sy <= 0); /* sign of gradient must not change */
if (sx*(long)sx+sy*(long)sy > xx*xx+yy*yy) { /* begin with longer part */
x2 = x0; x0 = sx+x1; y2 = y0; y0 = sy+y1; cur = -cur; /* swap P0 P2 */
}
if (cur != 0) { /* no straight line */
xx += sx; xx *= sx = x0 < x2 ? 1 : -1; /* x step direction */
yy += sy; yy *= sy = y0 < y2 ? 1 : -1; /* y step direction */
xy = 2*xx*yy; xx *= xx; yy *= yy; /* differences 2nd degree */
if (cur*sx*sy < 0) { /* negated curvature? */
xx = -xx; yy = -yy; xy = -xy; cur = -cur;
}
dx = 4.0*sy*cur*(x1-x0)+xx-xy; /* differences 1st degree */
dy = 4.0*sx*cur*(y0-y1)+yy-xy;
xx += xx; yy += yy; err = dx+dy+xy; /* error 1st step */
do {
setpixel(x0,y0); /* plot curve */
if (x0 == x2 && y0 == y2) return; /* last pixel -> curve finished */