二、基于QT_SDL渲染像素格式

本章目标:

  1. RGB与YUV的原理
  2. SDL渲染图像的方法
  3. QT渲染图像的方法
  4. SDL与QT结合渲染图像的方法
  5. 刷新SDL渲染的方式
  6. 面向对象工厂模式封装方法
  7. 窗口大小的变化与抗锯齿的处理

RGB像素格式原理

RGB与YUV像素格式基础

1. 渲染RGB像素格式

1.1 通过SDL渲染SDL窗口

  1. 创建SDL的窗口、渲染器、纹理
  2. 创建RGB图像数据
  3. 纹理通过渲染器渲染RGB数据到窗口
#include <iostream>
#include <sdl/SDL.h>
using namespace std;
#pragma comment(lib,"SDL2.lib")
#undef main
int main(int argc, char* argv[])
{
    int w = 800;
    int h = 600;
    //1 初始化SDL video库
    if (SDL_Init(SDL_INIT_VIDEO))
    {
        cout << SDL_GetError() << endl;
        return -1;
    }
    //2 生成SDL 窗口
    auto screen = SDL_CreateWindow("test sdl ffmpeg",
        SDL_WINDOWPOS_CENTERED,//窗口位置
        SDL_WINDOWPOS_CENTERED,
        w,h,
        SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE
    );
    if (!screen)
    {
        cout << SDL_GetError() << endl;
        return -2;
    }

    //3 生成渲染器
    auto render = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
    if (!render)
    {
        cout << SDL_GetError() << endl;
        return -3;
    }
    //4 生成材质
    auto texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_ARGB8888,
        SDL_TEXTUREACCESS_STREAMING,// 可加锁
        w, h
    );
    if (!texture)
    {
        cout << SDL_GetError() << endl;
        return -4;
    }

    // 存放图像的数据
    shared_ptr<unsigned char> rgb(new unsigned char[w * h * 4]);
    auto r = rgb.get();
    unsigned char tmp = 255;
    for (;;)
    {
        //判断退出
        SDL_Event ev;
        SDL_WaitEventTimeout(&ev, 10);
        if (ev.type == SDL_QUIT)
        {
            SDL_DestroyWindow(screen);
            break;
        }
        tmp--;
    
        for (int j = 0; j < h; j++)
        {
            int b = j * w * 4;
            for (int i = 0; i < w*4; i += 4)
            {
                r[b + i] = 0;           //B
                r[b + i + 1] = 0;       //G
                r[b + i + 2] = tmp;     //R
                r[b + i + 3] = 0;       //A
            }
        }
        //5 内存数据写入材质
        SDL_UpdateTexture(texture, NULL, r, w * 4);

        //6 清理屏幕
        SDL_RenderClear(render);
        SDL_Rect sdl_rect;
        sdl_rect.x = 0;
        sdl_rect.y = 0;
        sdl_rect.w = w;
        sdl_rect.h = h;
    
        //7 复制材质到渲染器
        SDL_RenderCopy(render, texture,
            NULL,//原图位置和尺寸
            NULL//&sdl_rect//目标位置和尺寸
        );
        //8 渲染
        SDL_RenderPresent(render);
    }
    getchar();
    return 0;
}

1.2 通过SDL渲染QT控件、通过定时器刷新渲染窗口

  1. 创建QT Lebel控件,及设置其标题、尺寸等属性
  2. SDL的窗口创建参数改自定义为Leble控件
  3. 重载定时器处理事件,内容为渲染图像
  4. 设置并启动定时器
#include "sdlqtrgb.h"
#include <sdl/SDL.h>
#pragma comment(lib,"SDL2.lib")
static SDL_Window* sdl_win = NULL;
static SDL_Renderer* sdl_render = NULL;
static SDL_Texture* sdl_texture = NULL;
static int sdl_width = 0;
static int sdl_height = 0;
static unsigned  char* rgb = NULL;
static int pix_size = 4;
void SdlQtRGB::timerEvent(QTimerEvent* ev)
{
    static unsigned char tmp = 255;
    tmp--;
    for (int j = 0; j < sdl_height; j++)
    {
        int b = j * sdl_width* pix_size;
        for (int i = 0; i < sdl_width * pix_size; i += pix_size)
        {
            rgb[b + i] = 0;         //B
            rgb[b + i + 1] = tmp;   //G
            rgb[b + i + 2] = 0;     //R
            rgb[b + i + 3] = 0;     //A
        }
    }
    SDL_UpdateTexture(sdl_texture, NULL, rgb, sdl_width * pix_size);
    SDL_RenderClear(sdl_render);
    SDL_Rect rect;
    rect.x = 0;
    rect.y = 0;
    rect.w = sdl_width;
    rect.h = sdl_height;
    SDL_RenderCopy(sdl_render,sdl_texture,NULL,&rect);
    SDL_RenderPresent(sdl_render);
}

SdlQtRGB::SdlQtRGB(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    sdl_width = ui.label->width();
    sdl_height = ui.label->height();
    //初始化SDL
    SDL_Init(SDL_INIT_VIDEO);
    //创建窗口
    sdl_win = SDL_CreateWindowFrom((void*)ui.label->winId());
    //创建渲染器
    sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);
    //创建材质
    sdl_texture = SDL_CreateTexture(sdl_render,
        SDL_PIXELFORMAT_ARGB8888,
        SDL_TEXTUREACCESS_STREAMING,
        sdl_width,
        sdl_height
    );
    rgb = new unsigned char[sdl_width * sdl_height * pix_size];
    startTimer(10);
}

1.3 合并两幅RGB图像

  1. 重新定义要渲染的纹理大小:宽为两张图相加,高为最高的图高度。
  2. 遍历两张图片的每一行,将B图片的第n行像素拷贝到A图片的第n行之后
  3. 保存合并的图片,并渲染显示。

#include "sdlqtrgb.h"
#include <QMessageBox>
#include <sdl/SDL.h>
#pragma comment(lib,"SDL2.lib")
static SDL_Window* sdl_win = NULL;
static SDL_Renderer* sdl_render = NULL;
static SDL_Texture* sdl_texture = NULL;
static int sdl_width = 0;
static int sdl_height = 0;
static unsigned  char* rgb = NULL;
static int pix_size = 4;
void SdlQtRGB::timerEvent(QTimerEvent* ev)
{
    static unsigned char tmp = 255;
    tmp--;
    for (int j = 0; j < sdl_height; j++)
    {
        int b = j * sdl_width* pix_size;
        for (int i = 0; i < sdl_width * pix_size; i += pix_size)
        {
            //rgb[b + i] = 0;         //B
            //rgb[b + i + 1] = tmp;   //G
            //rgb[b + i + 2] = 0;     //R
            //rgb[b + i + 3] = 0;     //A
        }
    }
    SDL_UpdateTexture(sdl_texture, NULL, rgb, sdl_width * pix_size);
    SDL_RenderClear(sdl_render);
    SDL_Rect rect;
    rect.x = 0;
    rect.y = 0;
    rect.w = sdl_width;
    rect.h = sdl_height;
    SDL_RenderCopy(sdl_render,sdl_texture,NULL,&rect);
    SDL_RenderPresent(sdl_render);
}

SdlQtRGB::SdlQtRGB(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
    sdl_width = ui.label->width();
    sdl_height = ui.label->height();
    //初始化SDL
    SDL_Init(SDL_INIT_VIDEO);
    //创建窗口
    sdl_win = SDL_CreateWindowFrom((void*)ui.label->winId());
    //创建渲染器
    sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);

    QImage img1("001.png");
    QImage img2("002.png");
    if (img1.isNull() || img2.isNull())
    {
        QMessageBox::information(this, "", "open image failed!");
        return;
    }
    int out_w = img1.width() + img2.width();
    int out_h = img1.height();
    if (out_h < img2.height())out_h = img2.height();

    sdl_width = out_w;
    sdl_height = out_h;
    resize(sdl_width, sdl_height);
    ui.label->move(0, 0);
    ui.label->resize(sdl_width, sdl_height);

    //创建材质
    sdl_texture = SDL_CreateTexture(sdl_render,
        SDL_PIXELFORMAT_ARGB8888,
        SDL_TEXTUREACCESS_STREAMING,
        sdl_width,
        sdl_height
    );
    rgb = new unsigned char[sdl_width * sdl_height * pix_size];
    //默认设置为透明
    memset(rgb, 0, sdl_width * sdl_height * pix_size);

    //合并两幅图像
    for (int i = 0; i < sdl_height; i++)
    {
        int b = i * sdl_width * pix_size;
        if (i < img1.height())
            memcpy(rgb + b, img1.scanLine(i), img1.width() * pix_size);
        b += img1.width() * pix_size;
        if (i < img2.height())
            memcpy(rgb + b, img2.scanLine(i), img2.width() * pix_size);
    }
    QImage out(rgb, sdl_width, sdl_height, QImage::Format_ARGB32);
    out.save("out.png");
    startTimer(10);
}

2. 渲染YUV像素格式

2.1 通过刷新渲染来播放YUV数据

注意:渲染YUV与渲染RGB的区别

  1. 读取YUV数据时的数据量与YUV不同
  2. 更新纹理时,每一行的数据两也不同
  3. 上面的区别主要因为RGB与YUV再内存中的存储方式不同。

#include "sdlqtrgb.h"
#include <sdl/SDL.h>
#include <fstream>
#include <QMessageBox>
using namespace std;
#pragma comment(lib,"SDL2.lib")
static SDL_Window* sdl_win = NULL;
static SDL_Renderer* sdl_render = NULL;
static SDL_Texture* sdl_texture = NULL;
static int sdl_width = 0;
static int sdl_height = 0;
static unsigned  char* yuv = NULL;
static int pix_size = 2;
static ifstream yuv_file;
void SdlQtRGB::timerEvent(QTimerEvent* ev)
{
    yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
    //yuv 平面存储存储
    // yyyyyyyy uu vv
    SDL_UpdateTexture(sdl_texture, NULL, yuv, 
        sdl_width  //一行 y的字节数
    );
    SDL_RenderClear(sdl_render);
    SDL_Rect rect;
    rect.x = 0;
    rect.y = 0;
    rect.w = sdl_width;
    rect.h = sdl_height;
    SDL_RenderCopy(sdl_render,sdl_texture,NULL,&rect);
    SDL_RenderPresent(sdl_render);
}

SdlQtRGB::SdlQtRGB(QWidget *parent)
    : QWidget(parent)
{
    //打开yuv文件
    yuv_file.open("400_300_25.yuv", ios::binary);
    if (!yuv_file)
    {
        QMessageBox::information(this, "", "open yuv failed!");
        return;
    }
    ui.setupUi(this);
    sdl_width = 400; 
    sdl_height = 300;
    ui.label->resize(sdl_width, sdl_height);
    //初始化SDL
    SDL_Init(SDL_INIT_VIDEO);
    //创建窗口
    sdl_win = SDL_CreateWindowFrom((void*)ui.label->winId());
    //创建渲染器
    sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);
    //创建材质 支持YUV
    sdl_texture = SDL_CreateTexture(sdl_render,
        SDL_PIXELFORMAT_IYUV,
        SDL_TEXTUREACCESS_STREAMING,
        sdl_width,
        sdl_height
    );
    yuv = new unsigned char[sdl_width * sdl_height * pix_size];
    startTimer(10);
}

2.2 随窗口自动缩放、抗锯齿、SDL窗口退出

自动缩放画面
需要搞清楚窗口、Qt label、SDL_Rect之间的关系

  1. 窗口可以根据Qt Label来创建,也可以通过SDL自定义窗口
  2. 若是根据Qt的winId创建的窗口,则渲染画面显示在窗口中;若SDL自定义的窗口,则渲染在单独画面,而不是再自定义的窗口中
  3. SDL_Rect的大小代表画面的大小,NULL表示渲染全部。
  4. Qt Lable只有通过winId创建的窗口才有效,代表渲染的尺寸。
    若是根据Qt的winId创建的窗口:创建一个resize事件处理函数,当触发时,让lable的大小随窗口的大小变化,让SDL_Rect为NULL,画面就会随着label大小变化,lable也随着窗口大小变化,实现画面随着窗口大小变化;也可以让QtLable和SDL_Rect的width和height都随窗口的大小直接变化。
    若是SDL自定义的窗口:渲染画面与窗口不在同一个窗口,而且QtLable无意义,所以直接让SDL_Rect为NULL即可。因为二者不是在同一个窗口,所以虽然可以让SDL_Rect的width和height随着自定义窗口变化,但无法控制SDL_Rect本身渲染的窗口大小。
    抗锯齿:在创建纹理之前,设置渲染的质量
    SDL窗口退出:若是根据QtWinID创建的窗口,退出时可直接窗口;但SDL自定义的窗口和渲染窗口不是一个窗口,因此点击渲染窗口退出无效,只有点击自定义窗口才能推出。为了解决这个问题,可以通过SDL事件来解决,当sdl的事件是退出事件时,就销毁创建的窗口、渲染器、纹理。
#include "sdlqtrgb.h"
#include <sdl/SDL.h>
#include <fstream>
#include <iostream>
#include <QMessageBox>
using namespace std;
#pragma comment(lib,"SDL2.lib")
static SDL_Window* sdl_win = NULL;
static SDL_Renderer* sdl_render = NULL;
static SDL_Texture* sdl_texture = NULL;
static int sdl_width = 0;
static int sdl_height = 0;
static unsigned  char* yuv = NULL;
static int pix_size = 2;
static ifstream yuv_file;
static bool isExit;


void SdlQtRGB::timerEvent(QTimerEvent* ev)
{
    if (isExit)
    {
        return;
    }
    
    //SDL窗口推出
    SDL_Event qev;
    SDL_WaitEventTimeout(&qev, 1);
    if (qev.type == SDL_QUIT)
    {
        SDL_DestroyTexture(sdl_texture);
        sdl_texture = nullptr;
        SDL_DestroyRenderer(sdl_render);
        sdl_render = nullptr;
        SDL_DestroyWindow(sdl_win);
        sdl_win = nullptr;
        isExit = true;
        return;
    }
    
    yuv_file.read((char*)yuv, sdl_width * sdl_height * 1.5);
    //yuv 平面存储存储
    // yyyyyyyy uu vv
    SDL_UpdateTexture(sdl_texture, NULL, yuv, 
        sdl_width  //一行 y的字节数
    );
    SDL_RenderClear(sdl_render);
    //SDL_Rect rect;
    //rect.x = 0;
    //rect.y = 0;
    //rect.w = sdl_width;
    //rect.h = sdl_height;
    
    //随窗口自动缩放画面
    SDL_RenderCopy(sdl_render,sdl_texture,NULL, NULL);
    SDL_RenderPresent(sdl_render);
}

void SdlQtRGB::resizeEvent(QResizeEvent* ev)
{
    ui.label->move(0, 0);
    ui.label->resize(size());
}

SdlQtRGB::SdlQtRGB(QWidget *parent)
    : QWidget(parent)
{
    //打开yuv文件
    yuv_file.open("400_300_25.yuv", ios::binary);
    if (!yuv_file)
    {
        QMessageBox::information(this, "", "open yuv failed!");
        return;
    }
    ui.setupUi(this);
    sdl_width = 400; 
    sdl_height = 300;
    ui.label->move(0, 0);
    ui.label->resize(size());
    //初始化SDL
    SDL_Init(SDL_INIT_VIDEO);
    //抗锯齿
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
    //创建窗口
    //sdl_win = SDL_CreateWindowFrom((void*)ui.label->winId());
    sdl_win = SDL_CreateWindow("",
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED,
        sdl_width, sdl_height,
        SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
    );
    //创建渲染器
    sdl_render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_ACCELERATED);
    //创建材质 支持YUV
    sdl_texture = SDL_CreateTexture(sdl_render,
        SDL_PIXELFORMAT_IYUV,
        SDL_TEXTUREACCESS_STREAMING,
        sdl_width,
        sdl_height
    );
    yuv = new unsigned char[sdl_width * sdl_height * pix_size];
 
    isExit = false;
    startTimer(10);
}

3. 面向对象封装

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值