OpenGL中的图像缩放(通过KeepAspectRatioByExpanding)

17
我正在尝试使用OpenGL实现图像缩放,只使用glTexCoord2f()glVertex2f()

让我解释一下:在使用glTexImage2D()QImage加载到GPU后,我必须根据Qt的规范执行图像缩放操作。 Qt定义了以下三个操作(见下图):

enter image description here

我认为这是唯一的方法,因为我的应用程序是Qt插件,需要在类的paint()方法内完成此任务。 IgnoreAspectRatio操作非常直观,它现在可以正常工作。 KeepAspectRatio最初给我带来了一些麻烦,但现在也可以正常工作。不幸的是,KeepAspectRatioByExpanding让我头痛。

我分享了迄今为止所做的内容,并感谢您对此问题的帮助:

main.cpp:

#include "oglWindow.h"
#include <QtGui/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    oglWindow w;
    w.show();
    return a.exec();
}

oglWindow.cpp:

#include "oglWindow.h"
#include "glwidget.h"

#include <QGridLayout>

oglWindow::oglWindow(QWidget *parent, Qt::WFlags flags)
    : QMainWindow(parent, flags)
{
    ui.setupUi(this);
    GLWidget *openGL = new GLWidget(this);

    QGridLayout *layout = new QGridLayout;
    setLayout(layout);
}

oglWindow::~oglWindow()
{
}

oglWindow.h:

#ifndef oglWindow_H
#define oglWindow_H

#include <QtGui/QMainWindow>
#include "ui_yuv_to_rgb.h"

class oglWindow : public QMainWindow
{
    Q_OBJECT

public:
    oglWindow(QWidget *parent = 0, Qt::WFlags flags = 0);
    ~oglWindow();

private:
    Ui::oglWindowClass ui;
};

#endif // oglWindow_H

glwidget.cpp:

#ifdef _MSC_VER
    #include <windows.h>
    #include <GL/glew.h>
    #include <GL/gl.h>  
#else
    #include <GL/gl.h>
#endif

#include "glwidget.h"

#include <QDebug>

#include <iostream>
#include <fstream>
#include <assert.h>


static const char *p_s_fragment_shader =
    "#extension GL_ARB_texture_rectangle : enable\n"
    "uniform sampler2DRect tex;"
    "uniform float ImgHeight, chromaHeight_Half, chromaWidth;"
    "void main()"
    "{"
    "    vec2 t = gl_TexCoord[0].xy;" // get texcoord from fixed-function pipeline
    "    float CbY = ImgHeight + floor(t.y / 4.0);"
    "    float CrY = ImgHeight + chromaHeight_Half + floor(t.y / 4.0);"
    "    float CbCrX = floor(t.x / 2.0) + chromaWidth * floor(mod(t.y, 2.0));"
    "    float Cb = texture2DRect(tex, vec2(CbCrX, CbY)).x - .5;"
    "    float Cr = texture2DRect(tex, vec2(CbCrX, CrY)).x - .5;"
    "    float y = texture2DRect(tex, t).x;" // redundant texture read optimized away by texture cache
    "    float r = y + 1.28033 * Cr;"
    "    float g = y - .21482 * Cb - .38059 * Cr;"
    "    float b = y + 2.12798 * Cb;"
    "    gl_FragColor = vec4(r, g, b, 1.0);"
    "}";


GLWidget::GLWidget(QWidget *parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent), _frame(NULL)
{
    setAutoFillBackground(false);
    setMinimumSize(640, 480);

    /* Load 1280x768 YV12 frame from the disk */

    _frame = new QImage(1280, 768, QImage::Format_RGB888);  
    if (!_frame)
    {
        qDebug() << "> GLWidget::GLWidget !!! Failed to create _frame";
        return;
    }

    std::ifstream yuv_file("bloco.yv12", std::ios::in | std::ios::binary | std::ios::ate);
    if (!yuv_file.is_open())
    {
        qDebug() << "> GLWidget::GLWidget !!! Failed to load yuv file";
        return;
    }
    int yuv_file_sz = yuv_file.tellg();

    unsigned char* memblock = new unsigned char[yuv_file_sz];
    if (!memblock)
    {
        qDebug() << "> GLWidget::GLWidget !!! Failed to allocate memblock";
        return;
    }

    yuv_file.seekg(0, std::ios::beg);
    yuv_file.read((char*)memblock, yuv_file_sz);
    yuv_file.close();

    qMemCopy(_frame->scanLine(0), memblock, yuv_file_sz);

    delete[] memblock;
}

GLWidget::~GLWidget()
{
    if (_frame)
        delete _frame;
}

void GLWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    qDebug() << "> GLWidget::paintEvent OpenGL:"  << ((painter.paintEngine()->type() != QPaintEngine::OpenGL &&
                                       painter.paintEngine()->type() != QPaintEngine::OpenGL2) ? "disabled" : "enabled");   

    QGLContext* context = const_cast<QGLContext *>(QGLContext::currentContext());
    if (!context)
    {
        qDebug() << "> GLWidget::paintEvent !!! Unable to retrieve OGL context";
        return;
    }
    context->makeCurrent();

    painter.fillRect(QRectF(QPoint(0, 0), QSize(1280, 768)), Qt::black);

    painter.beginNativePainting();

    /* Initialize GL extensions */
    GLenum err = glewInit();
    if (err != GLEW_OK)
    {
        qDebug() << "> GLWidget::paintEvent !!! glewInit failed with: " << err;
        return;
    }
    if (!GLEW_VERSION_2_1)  // check that the machine supports the 2.1 API.
    {
        qDebug() << "> GLWidget::paintEvent !!! System doesn't support GLEW_VERSION_2_1";
        return;
    }

    /* Setting up texture and transfering data to the GPU */

    static GLuint texture = 0;
    if (texture != 0)
    {
        context->deleteTexture(texture);
    }

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture);

    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
            GL_LUMINANCE, _frame->width(), _frame->height() + _frame->height() / 2, 0,
            GL_LUMINANCE, GL_UNSIGNED_BYTE, _frame->bits());

    assert(glGetError() == GL_NO_ERROR);

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glEnable(GL_TEXTURE_RECTANGLE_ARB);

    glClearColor(0.3, 0.3, 0.4, 1.0);

    int img_width = _frame->width();
    int img_height = _frame->height();
    int offset_x = 0;
    int offset_y = 0;

    GLfloat gl_width = width(); // GL context size
    GLfloat gl_height = height();

    /* Initialize shaders and execute them */   
    _init_shaders();

    qDebug() << "paint(): gl_width:" << gl_width << " gl_height:" << gl_height <<  
        " img:" << _frame->width() << "x" << _frame->height();

    int fill_mode = 1;
    switch (fill_mode)
    {
        case 0: // KeepAspectRatioByExpanding
        {
            // need help!
        }
        break;

        case 1: // IgnoreAspectRatio
        {
            // Nothing special needs to be done for this operation.
        }
        break;

        case 2: // KeepAspectRatio
        default:
        {
          // Compute aspect ratio and offset Y for widescreen borders
            double ratiox = img_width/gl_width;
            double ratioy = img_height/gl_height;

            if (ratiox > ratioy)
            {
                gl_height = qRound(img_height / ratiox);
                offset_y = qRound((height() - gl_height) / 2);
                gl_height += offset_y * 2;
            }
            else
            {
                gl_width = qRound(img_width / ratioy);
                offset_x = qRound((width() - gl_width) / 2);
                gl_width += offset_x * 2;
            }
        }
        break;
    }


    // Mirroring texture coordinates to flip the image vertically
    glBegin(GL_QUADS);  
    glTexCoord2f(0.0f, img_height);                  glVertex2f(offset_x, gl_height - offset_y);
    glTexCoord2f(img_width, img_height);             glVertex2f(gl_width - offset_x, gl_height - offset_y);
    glTexCoord2f(img_width, 0.0f);                   glVertex2f(gl_width - offset_x, offset_y);
    glTexCoord2f(0.0f, 0.0f);                        glVertex2f(offset_x, offset_y);
    glEnd();

    painter.endNativePainting();
}

void GLWidget::_init_shaders()
{       
    int f = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(f, 1, &p_s_fragment_shader, 0);
    glCompileShader(f);

    _shader_program = glCreateProgram();
    glAttachShader(_shader_program, f);
    glLinkProgram(_shader_program);

    glUseProgram(_shader_program);

    glUniform1i(glGetUniformLocation(_shader_program, "tex"), 0);
    glUniform1f(glGetUniformLocation(_shader_program, "ImgHeight"), _frame->height());
    glUniform1f(glGetUniformLocation(_shader_program, "chromaHeight_Half"), (_frame->height() / 2) / 2);
    glUniform1f(glGetUniformLocation(_shader_program, "chromaWidth"), _frame->width() / 2);   
}

glwidget.h:

#include <QtOpenGL/QGLWidget>
#include <QtGui/QImage>
#include <QPainter>

class GLWidget : public QGLWidget
{
    Q_OBJECT

public:
    GLWidget(QWidget *parent = 0);
    ~GLWidget();

    void paintEvent(QPaintEvent *event);    

private:
    void _init_shaders();
    bool _checkShader(int n_shader_object);

    QImage* _frame;
    int _shader_program;
};

您可以在这里下载数据文件


理想的答案需要提供可工作的源代码。 - karlphillip
2个回答

7

只需按照与KeepAspectRatio相同的方式进行计算,但这次保持高度而非宽度。您将获得一个大于1的宽度,因此您必须使用其倒数作为纹理坐标。


是否使用规范化坐标并不重要,您只需为纹理坐标添加这些因素即可。为了使其起作用,我假设图像填充整个纹理(从(0,0)到(1,1);这使得将值乘以您的宽度/高度变得容易)。纹理包装必须关闭。

默认纹理坐标:

(0,0)-(1,0)
|     |
(0,1)-(1,1)

宽高比:

tex_ar = tex_width / tex_height; // aspect ratio for the texture being used
scr_ar = scr_width / scr_height; // aspect ratio for the quad being drawn

保持纵横比(从内部触摸):
(0, 0)-----------------------(max(1, scr_ar / tex_ar), 0)
|                            |
(0, max(1, tex_ar / scr_ar))-(max(1, scr_ / tex_ar), max(1, tex_ar / scr_ar))

保持宽高比扩展(从外部触摸;只需将max()替换为min()):
(0, 0)-----------------------(min(1, scr_ar / tex_ar), 0)
|                            |
(0, min(1, tex_ar / scr_ar))-(min(1, scr_ / tex_ar), min(1, tex_ar / scr_ar))

对于您的情况,您只需要将生成的纹理坐标乘以您的宽度/高度。


1
你能给个例子吗?正如你所见,我没有使用标准化坐标。屏幕大小为640x480,图像大小为1280x768。 - karlphillip
1
那样行不通。我在KeepAspectRatio中进行的计算只是为了知道开始绘制图像的Y偏移量(并放置宽屏边框)。然后,在glVertex2f()调用中使用此偏移量以实现此效果。据我所知,对于KeepAspectRatioByExpanding,我需要改变图像的大小,然后可能改变一些glTexCoord2f()值而不是顶点坐标。 - karlphillip
太棒了!我开始理解了,但仍然无法弄清楚。你介意添加代码使其成为完美的答案吗?我刚刚尝试了一些东西,但它没有起作用 :( - karlphillip
目前没有安装QT,所以可能稍后再说。但基本上你只需要将生成的纹理坐标乘以纹理的宽度/高度,例如宽度变为width_to_draw = (tex_width * max(1, scr_ar / tex_ar), 0)。虽然我不确定QT如何处理大于1的纹理坐标。因此,当不使用完整的宽度或高度时,可能需要缩小渲染的四边形。 - Mario
请检查您刚刚分享的代码,它似乎不正确:width_to_draw = (tex_width * result_of_max, 0) - karlphillip
显示剩余4条评论

5
您可以简单地复制“保持纵横比”分支(前提是它能够正常工作),然后只需翻转比例比较符号即可,例如:
if (ratiox > ratioy)

变成

if (ratiox <= ratioy)

但我不确定它是否真正起作用(比率计算一直困扰着我 - 而你的是棘手的),而且我现在没有Qt,所以无法尝试。但应该可以做到。请注意,图像将居中显示(而不是左对齐,如您的图像所示),但这很容易修复。

编辑

这是在GLUT应用程序中工作的源代码(没有QT,抱歉):

static void DrawObject(void)
{ 
    int img_width = 1280;//_frame->width();
    int img_height = 720;//_frame->height();
    GLfloat offset_x = -1;
    GLfloat offset_y = -1;

    int p_viewport[4];
    glGetIntegerv(GL_VIEWPORT, p_viewport); // don't have QT :'(

    GLfloat gl_width = p_viewport[2];//width(); // GL context size
    GLfloat gl_height = p_viewport[3];//height();

    int n_mode = 0;
    switch(n_mode) {
    case 0: // KeepAspectRatioByExpanding
        {
            float ratioImg = float(img_width) / img_height;
            float ratioScreen = gl_width / gl_height;

            if(ratioImg < ratioScreen) {
                gl_width = 2;
                gl_height = 2 * ratioScreen / ratioImg;
            } else {
                gl_height = 2;
                gl_width = 2 / ratioScreen * ratioImg;
            }
            // calculate image size
        }
        break;

    case 1: // IgnoreAspectRatio
        gl_width = 2;
        gl_height = 2;
        // OpenGL normalized coordinates are -1 to +1 .. hence width (or height) = +1 - (-1) = 2
        break;

    case 2: // KeepAspectRatio
        {
            float ratioImg = float(img_width) / img_height;
            float ratioScreen = gl_width / gl_height;

            if(ratioImg > ratioScreen) {
                gl_width = 2;
                gl_height = 2 * ratioScreen / ratioImg;
            } else {
                gl_height = 2;
                gl_width = 2 / ratioScreen * ratioImg;
            }
            // calculate image size

            offset_x = -1 + (2 - gl_width) * .5f;
            offset_y = -1 + (2 - gl_height) * .5f;
            // center on screen
        }
        break;
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode( GL_MODELVIEW );
    glLoadIdentity();
    // just simple ortho view, no fancy transform ...

    glBegin(GL_QUADS);
        glTexCoord2f(0, 0);
        glVertex2f(offset_x, offset_y);

        glTexCoord2f(ImgWidth, 0);
        glVertex2f(offset_x + gl_width, offset_y);

        glTexCoord2f(ImgWidth, ImgHeight);
        glVertex2f(offset_x + gl_width, offset_y + gl_height);

        glTexCoord2f(0, ImgHeight);
        glVertex2f(offset_x, offset_y + gl_height);
    glEnd();
    // draw a single quad
}

这是通过比较屏幕宽高比和图像宽高比来实现的。实际上,您正在比较图像宽度与屏幕宽度的比率以及图像高度与屏幕高度的比率。这至少是可疑的,更不用说是错误的了。
此外,标准化的OpenGL坐标(提供简单的正交视图)在范围内为(-1,-1)到(1,1)。这意味着标准化的宽度和高度都是2,偏移量是(-1,-1)。其余的代码应该是不言自明的。如果纹理被翻转(我使用了一种通用纹理进行测试,不确定它是否是正立的),只需在相应方向上更改纹理坐标(将0换成ImgWidth(或高度),反之亦然)。 编辑2

使用像素坐标(而不是使用标准化的OpenGL坐标)更加简单。您可以使用:

static void DrawObject(void)
{ 
    int img_width = 1280;//_frame->width();
    int img_height = 720;//_frame->height();
    GLfloat offset_x = 0;
    GLfloat offset_y = 0;

    int p_viewport[4];
    glGetIntegerv(GL_VIEWPORT, p_viewport);

    GLfloat gl_width = p_viewport[2];//width(); // GL context size
    GLfloat gl_height = p_viewport[3];//height();

    int n_mode = 0;
    switch(n_mode) {
    case 0: // KeepAspectRatioByExpanding
        {
            float ratioImg = float(img_width) / img_height;
            float ratioScreen = gl_width / gl_height;

            if(ratioImg < ratioScreen)
                gl_height = gl_width / ratioImg;
            else
                gl_width = gl_height * ratioImg;
            // calculate image size
        }
        break;

    case 1: // IgnoreAspectRatio
        break;

    case 2: // KeepAspectRatio
        {
            float ratioImg = float(img_width) / img_height;
            float ratioScreen = gl_width / gl_height;

            GLfloat orig_width = gl_width;
            GLfloat orig_height = gl_height;
            // remember those to be able to center the quad on screen

            if(ratioImg > ratioScreen)
                gl_height = gl_width / ratioImg;
            else
                gl_width = gl_height * ratioImg;
            // calculate image size

            offset_x = 0 + (orig_width - gl_width) * .5f;
            offset_y = 0 + (orig_height - gl_height) * .5f;
            // center on screen
        }
        break;
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(-1, -1, 0);
    glScalef(2.0f / p_viewport[2], 2.0f / p_viewport[3], 1.0);
    // just simple ortho view for vertex coordinate to pixel matching

    glBegin(GL_QUADS);
        glTexCoord2f(0, 0);
        glVertex2f(offset_x, offset_y);

        glTexCoord2f(img_width, 0);
        glVertex2f(offset_x + gl_width, offset_y);

        glTexCoord2f(img_width, img_height);
        glVertex2f(offset_x + gl_width, offset_y + gl_height);

        glTexCoord2f(0, img_height);
        glVertex2f(offset_x, offset_y + gl_height);
    glEnd();
    // draw a single quad
}

请注意,这两个版本的代码都使用NPOT纹理。要使代码适应您的对象,您可以执行以下操作:
void GLWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    qDebug() << "> GLWidget::paintEvent OpenGL:"  << ((painter.paintEngine()->type() != QPaintEngine::OpenGL &&
                                       painter.paintEngine()->type() != QPaintEngine::OpenGL2) ? "disabled" : "enabled");   

    QGLContext* context = const_cast<QGLContext *>(QGLContext::currentContext());
    if (!context)
    {
        qDebug() << "> GLWidget::paintEvent !!! Unable to retrieve OGL context";
        return;
    }
    context->makeCurrent();

    painter.fillRect(QRectF(QPoint(0, 0), QSize(1280, 768)), Qt::black);

    painter.beginNativePainting();

    /* Initialize GL extensions */
    GLenum err = glewInit();
    if (err != GLEW_OK)
    {
        qDebug() << "> GLWidget::paintEvent !!! glewInit failed with: " << err;
        return;
    }
    if (!GLEW_VERSION_2_1)  // check that the machine supports the 2.1 API.
    {
        qDebug() << "> GLWidget::paintEvent !!! System doesn't support GLEW_VERSION_2_1";
        return;
    }

    /* Setting up texture and transfering data to the GPU */

    static GLuint texture = 0;
    if (texture != 0)
    {
        context->deleteTexture(texture);
    }

    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture);

    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0,
            GL_LUMINANCE, _frame->width(), _frame->height() + _frame->height() / 2, 0,
            GL_LUMINANCE, GL_UNSIGNED_BYTE, _frame->bits());

    assert(glGetError() == GL_NO_ERROR);

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glEnable(GL_TEXTURE_RECTANGLE_ARB);

    glClearColor(0.3, 0.3, 0.4, 1.0);

    /* Initialize shaders and execute them */   
    _init_shaders();

    int img_width = _frame->width();
    int img_height = _frame->height();
    GLfloat offset_x = 0;
    GLfloat offset_y = 0;
    GLfloat gl_width = width(); // GL context size
    GLfloat gl_height = height();

    qDebug() << "paint(): gl_width:" << gl_width << " gl_height:" << gl_height <<  
        " img:" << _frame->width() << "x" << _frame->height();

    int fill_mode = 0;
    switch(fill_mode) {
    case 0: // KeepAspectRatioByExpanding
        {
            float ratioImg = float(img_width) / img_height;
            float ratioScreen = gl_width / gl_height;

            if(ratioImg < ratioScreen)
                gl_height = gl_width / ratioImg;
            else
                gl_width = gl_height * ratioImg;
            // calculate image size
        }
        break;

    case 1: // IgnoreAspectRatio
        break;

    case 2: // KeepAspectRatio
        {
            float ratioImg = float(img_width) / img_height;
            float ratioScreen = gl_width / gl_height;

            GLfloat orig_width = gl_width;
            GLfloat orig_height = gl_height;
            // remember those to be able to center the quad on screen

            if(ratioImg > ratioScreen)
                gl_height = gl_width / ratioImg;
            else
                gl_width = gl_height * ratioImg;
            // calculate image size

            offset_x = 0 + (orig_width - gl_width) * .5f;
            offset_y = 0 + (orig_height - gl_height) * .5f;
            // center on screen
        }
        break;
    }

    glDisable(GL_CULL_FACE); // might cause problems if enabled
    glBegin(GL_QUADS);
        glTexCoord2f(0, 0);
        glVertex2f(offset_x, offset_y);

        glTexCoord2f(img_width, 0);
        glVertex2f(offset_x + gl_width, offset_y);

        glTexCoord2f(img_width, img_height);
        glVertex2f(offset_x + gl_width, offset_y + gl_height);

        glTexCoord2f(0, img_height);
        glVertex2f(offset_x, offset_y + gl_height);
    glEnd();
    // draw a single quad

    painter.endNativePainting();
}

由于我没有QT,所以无法保证最后一个代码片段是无错误的。但如果有任何打字错误,应该很容易修复。


1
+1 实际上,目前我正是通过这种方式计算宽高比,并在 640x480 的表面上完美地显示 1280x720 视频。 然而,当视频被绘制在 240x320 的表面上时,结果图像没有被正确地裁剪。在这种情况下,视频将以 480x320 的形式显示,即占用了屏幕中更多的空间。这意味着我仍然缺少一些东西,而这个 bug 可能与 glVertex2f 的坐标有关。 - karlphillip
480x320 - 你指的是哪个代码?是 keepAspectRatio 版本吗?如果是,我可以提供一个可工作的版本的源代码。 - the swine
我在谈论KeepAspectRatioByExpanding模式,这是我需要帮助的地方。根据您的答案,我复制了KeepAspectRatio的代码并翻转了比例比较符号。这提供了正确的AR比例,但是,在glVertex2f调用中还有其他事情需要做,以使视频正确裁剪/裁剪。 - karlphillip
1
@karlphillip 在上面添加了源代码。这是GLUT(非QT),但在QT中应该同样适用。如果您需要完整的源代码,我也可以发布它。 - the swine
谢谢你的回答,我昨天已经点赞了。然而,它并没有对我有太大帮助,而且我在悬赏声明中明确表示我需要解决问题的可行源代码。这就是为什么我分享了整个应用程序的完整源代码。 - karlphillip
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接