一、前言
本文旨在给一个jpg图片添加一个黑色边框,比如原jpg图片的分辨率为128 * 128,需要用它生成一张1280 * 720分辨率的图片,该生成的图片样式为jpg图片放置在正中间、周围填充黑色背景。类似于给图片添加黑色边框的效果。涉及的知识有,yuv420p格式,YUV分量的对应关系,以及YUV在内存中的存储格式,libjpeg-turbo库的使用。
二、libjpeg-turbo解码jpeg图片
读出jpeg图片数据后,使用libjpeg-turbo接口对其进行解码,生成为YUV格式数据。
int UserHeadImage::decode2Yuv()
{
QByteArray jpeg;
QByteArray yuv;
int yuv_type;
readImage(jpeg);
tjhandle handle = NULL;
int subsample, colorspace;
int flags = 0;
int padding = 1;
int ret = 0;
handle = tjInitDecompress();
tjDecompressHeader3(handle, (unsigned char*)jpeg.data(), jpeg.size(), &yuvWidth, &yuvHeight, &subsample, &colorspace);
qDebug("w: %d h: %d subsample: %d color: %d\n", yuvWidth, yuvHeight, subsample, colorspace);
flags |= 0;
yuv_type = subsample;
yuvBufferSize = tjBufSizeYUV2(yuvWidth, padding, yuvHeight, subsample);
if(yuvBuffer)
{
free(yuvBuffer);
yuvBuffer = NULL;
}
yuvBuffer = (char *)malloc(yuvBufferSize);
if (yuvBuffer == NULL)
{
qDebug("malloc buffer for rgb failed.\n");
return -1;
}
ret = tjDecompressToYUV2(handle, (unsigned char*)jpeg.data(), jpeg.size(), (unsigned char*)yuvBuffer, yuvWidth,
padding, yuvHeight, flags);
if (ret < 0)
{
qDebug("compress to jpeg failed: %s\n", tjGetErrorStr());
}
tjDestroy(handle);
return ret;
}
三、Yuv420编码格式
yuv420P格式 Y、U、V分量的对应关系,如下图所示。
-
四个Y共用一组UV。
-
四个Y由两个相邻的行组成。
YUV分量在内存中的存放方式,如下图所示。 -
Y、U、V三个分量数据分开存放。
-
先存放Y分量数据,长度为w*h。
-
然后存放U分量数据,长度为w*h/4。
-
最后存放V分量数据,长度为w*h/4。
四、生成目标形式
对解码后的Y、U、V数据进行处理,生成目标形式。目标图像宽高为VIDEO_TRANS_WIDTH * VIDEO_TRANS_HEIGHT。
- 计算出源图像在目标图像中的位置,然后以像素点扫描目标图像,每一个像素点对应一组YUV数据。
- 然后根据上面的Y、U、V分量的对应关系和在内存中存放的位置,给目标图像填充Y、U、V数据。
- 如果扫描到的像素点为源图像在目标图像中的位置区域时,拷贝源图像的Y、U、V数据到相应位置;如果不是,则在相应位置处填充“黑色”背景的Y、U、V数据。
比如目标图像的大小为1280 * 720,源图像大小为128 * 128。
- 源图像需要存放在目标图像的居中位置,则其存放区域(x0,y0,x1,y1)为((1280-128)/ 2,(720-128)/ 2,(1280-128)/ 2 + 128,(1280-128)/ 2 + 128)。
- 对应Y分量,扫描到以上区域后拷贝源图像数据到目标位置,其他区域上的填充0x00(黑色)。
- 对应UV分量,在一行上每两个Y写入一组UV,每两行执行一次写入。扫描到以上区域后拷贝源图像数据到目标位置,其他区域上的填充0x80。
源图像
目标图像
void UserHeadImage::addBlackBorder()
{
// yuvBuffer -> transmissionData
// 添加黑色边框,yuv原始大小(128*128)转为 VIDEO_TRANS_WIDTH*VIDEO_TRANS_HEIGHT 大小
char *sY = yuvBuffer;
char *sU = sY + yuvWidth*yuvHeight;
char *sV = sU + yuvWidth*yuvHeight/4;
char *dY = transmissionData;
char *dU = dY + VIDEO_TRANS_WIDTH*VIDEO_TRANS_HEIGHT;
char *dV = dU + VIDEO_TRANS_WIDTH*VIDEO_TRANS_HEIGHT/4;
int srcYIndex = 0;
int srcUVIndex = 0;
int dstYIndex = 0;
int dstUVIndex = 0;
for (int j = 0; j < VIDEO_TRANS_HEIGHT; j++) {
for (int k = 0; k < VIDEO_TRANS_WIDTH; k++) {
if ( (j >= ((VIDEO_TRANS_HEIGHT)-yuvHeight)/2 && j < (((VIDEO_TRANS_HEIGHT)-yuvHeight)/2 + yuvHeight))
&& (k >= ((VIDEO_TRANS_WIDTH)-yuvWidth)/2 && k < (((VIDEO_TRANS_WIDTH)-yuvWidth)/2 + yuvWidth)) )
{
// 读取src图像的Y数据
dY[dstYIndex] = sY[srcYIndex];
srcYIndex++;
dstYIndex++;
}
else
{
dY[dstYIndex] = 0x00;
dstYIndex++;
}
// 写入 u v 数据,每两个y写入一组u v
if(dstYIndex % 2 == 0)
{
// 隔行存放
if((j+1)%2 == 0)
{
continue;
}
if ( (j >= ((VIDEO_TRANS_HEIGHT)-yuvHeight)/2 && j < (((VIDEO_TRANS_HEIGHT)-yuvHeight)/2 + yuvHeight))
&& (k >= ((VIDEO_TRANS_WIDTH)-yuvWidth)/2 && k < (((VIDEO_TRANS_WIDTH)-yuvWidth)/2 + yuvWidth)) )
{
dU[dstUVIndex] = sU[srcUVIndex];
dV[dstUVIndex] = sV[srcUVIndex];
srcUVIndex++;
dstUVIndex++;
}
else
{
dU[dstUVIndex] = 0x80;
dV[dstUVIndex] = 0x80;
dstUVIndex++;
}
}
}
}
}
五、libjpeg-turbo编码yuv生成jpeg
int UserHeadImage::tyuv2jpeg(unsigned char* yuv_buffer, int yuv_size, int width, int height, int subsample, unsigned char** jpeg_buffer, unsigned long* jpeg_size, int quality)
{
tjhandle handle = NULL;
int flags = 0;
int padding = 1;
int need_size = 0;
int ret = 0;
handle = tjInitCompress();
flags |= 0;
need_size = tjBufSizeYUV2(width, padding, height, subsample);
if (need_size != yuv_size)
{
qDebug("detect yuv size: %d, give: %d, check again.\n", need_size, yuv_size);
return 0;
}
ret = tjCompressFromYUV(handle, yuv_buffer, width, padding, height, subsample, jpeg_buffer, jpeg_size, quality, flags);
if (ret < 0)
{
qDebug("compress to jpeg failed: %s\n", tjGetErrorStr());
}
tjDestroy(handle);
return ret;
}