《音视频:给图片添加黑色边框》

一、前言

本文旨在给一个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;
}

猜你喜欢

转载自blog.csdn.net/qq_40709487/article/details/129487735