Qt_Gif_Creator 基于Qt的屏幕gif录制工具

本文介绍了一个基于Qt框架的屏幕GIF录制工具的实现。该工具包含XYGifCreator类负责GIF创建逻辑,使用Gif.h库进行GIF编码;XYGifFrame类提供GUI界面,支持设置录制区域大小、帧率以及保存位置。工具采用多线程处理GIF编码,支持Windows和Linux平台,能够捕获屏幕内容并包含鼠标指针。实现功能包括:区域选择调整、GIF录制控制、系统托盘图标操作等。核心代码展示了如何通过Qt跨平台API获取屏幕截图,并针对不同操作系统处理鼠标指针绘制,最后将图像帧编码为GIF动画。

#include "xygifcreator.h"
#include <QImage>
#include <QThread>

#include "gif.h"

class XYGif : public QObject
{
    Q_OBJECT
public:
    explicit XYGif()
    {
        moveToThread(&mWorkThread);
    }
    ~XYGif()
    {
        if (mWorkThread.isRunning())
        {
            mWorkThread.quit();
            mWorkThread.wait();
        }
    }

public slots:
    void begin(const QString &file, int width, int height, int delay)
    {
        GifBegin(&mGifWriter, file.toUtf8().data(), static_cast<quint32>(width), static_cast<quint32>(height), static_cast<quint32>(delay));
    }
    void frame(const QImage &img, int width, int height, int delay)
    {
        GifWriteFrame(&mGifWriter, img.bits(),
                      static_cast<quint32>(qMin(width, img.width())),
                      static_cast<quint32>(qMin(height, img.height())),
                      static_cast<quint32>(100.0 / delay));
    }
    void end()
    {
        GifEnd(&mGifWriter);

        mWorkThread.quit();
    }

private:
    GifWriter mGifWriter;
    QThread   mWorkThread;

    friend class XYGifCreator;
};

XYGifCreator::XYGifCreator(QObject *parent)
    : QObject(parent)
{
    mGif = new XYGif;
    connect(&mGif->mWorkThread, &QThread::finished, this, &XYGifCreator::finished);
}

XYGifCreator::~XYGifCreator()
{
    delete mGif;
}

void XYGifCreator::startThread()
{
    mGif->mWorkThread.start();
}

bool XYGifCreator::isRunning()
{
    return mGif->mWorkThread.isRunning();
}

void XYGifCreator::begin(const QString &file, int width, int height, int delay, Qt::ConnectionType type)
{
    mWidth  = width;
    mHeight = height;

    QMetaObject::invokeMethod(mGif, "begin", type,
                              QGenericReturnArgument(),
                              Q_ARG(QString, file),
                              Q_ARG(int, width),
                              Q_ARG(int, height),
                              Q_ARG(int, delay));
}

void XYGifCreator::frame(const QImage &img, int delay, Qt::ConnectionType type)
{
    // gif.h 文件有描述目前只能是RGBA8888图片格式,并且alpha没有被使用
    QImage img32 = img.convertToFormat(QImage::Format_RGBA8888);

    QMetaObject::invokeMethod(mGif, "frame", type,
                              QGenericReturnArgument(),
                              Q_ARG(QImage, img32),
                              Q_ARG(int, mWidth),
                              Q_ARG(int, mHeight),
                              Q_ARG(int, delay));
}

void XYGifCreator::end(Qt::ConnectionType type)
{
    QMetaObject::invokeMethod(mGif, "end", type);
}

#include "xygifcreator.moc"
#include "xygifframe.h"
#include "xypackimage.h"
#include "ui_xygifframe.h"
#include <QPainter>
#include <QFileDialog>
#include <QApplication>
#include <QScreen>
#include <QResizeEvent>
#include <QDateTime>
#include <QMessageBox>
#include <QDebug>
#include <QMenu>

#ifdef Q_OS_WIN32
#include <windows.h>

static QImage getScreenImage(int x, int y, int width, int height)
{
    HDC hScrDC = ::GetDC(nullptr);
    HDC hMemDC = nullptr;

    BYTE *lpBitmapBits = nullptr;

    int nWidth = width;
    int nHeight = height;

    hMemDC = ::CreateCompatibleDC(hScrDC);

    BITMAPINFO bi;
    ZeroMemory(&bi, sizeof(BITMAPINFO));
    bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bi.bmiHeader.biWidth = nWidth;
    bi.bmiHeader.biHeight = nHeight;
    bi.bmiHeader.biPlanes = 1;
    bi.bmiHeader.biBitCount = 24;

    CURSORINFO hCur ;
    ZeroMemory(&hCur,sizeof(hCur));
    hCur.cbSize = sizeof(CURSORINFO);
    GetCursorInfo(&hCur);

    ICONINFO IconInfo = {};
    if(GetIconInfo(hCur.hCursor, &IconInfo))
    {
        hCur.ptScreenPos.x -= IconInfo.xHotspot;
        hCur.ptScreenPos.y -= IconInfo.yHotspot;
        if(nullptr != IconInfo.hbmMask)
            DeleteObject(IconInfo.hbmMask);
        if(nullptr != IconInfo.hbmColor)
            DeleteObject(IconInfo.hbmColor);
    }

    HBITMAP bitmap = ::CreateDIBSection(hMemDC, &bi, DIB_RGB_COLORS, (LPVOID*)&lpBitmapBits, nullptr, 0);
    HGDIOBJ oldbmp = ::SelectObject(hMemDC, bitmap);

    ::BitBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, x, y, SRCCOPY);
    DrawIconEx(hMemDC, hCur.ptScreenPos.x - x, hCur.ptScreenPos.y - y, hCur.hCursor, 0, 0, 0, nullptr, DI_NORMAL | DI_COMPAT);

    BITMAPFILEHEADER bh;
    ZeroMemory(&bh, sizeof(BITMAPFILEHEADER));
    bh.bfType = 0x4d42;  //bitmap
    bh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    bh.bfSize = bh.bfOffBits + ((nWidth*nHeight)*3);

    int size = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 3 * nWidth * nHeight;
    uchar *bmp = new uchar[size];
    uint offset = 0;
    memcpy(bmp, (char *)&bh, sizeof(BITMAPFILEHEADER));
    offset = sizeof(BITMAPFILEHEADER);
    memcpy(bmp + offset, (char *)&(bi.bmiHeader), sizeof(BITMAPINFOHEADER));
    offset += sizeof(BITMAPINFOHEADER);
    memcpy(bmp + offset, (char *)lpBitmapBits, 3 * nWidth * nHeight);

    ::SelectObject(hMemDC, oldbmp);
    ::DeleteObject(bitmap);
    ::DeleteObject(hMemDC);
    ::ReleaseDC(nullptr, hScrDC);
    QImage image = QImage::fromData(bmp, size);
    delete[] bmp;
    return image;
}
#else // linux x11
#include <X11/Xlib.h>
#include <X11/extensions/Xfixes.h>
static void X11drawCursor(Display *display, QImage &image, int recording_area_x, int recording_area_y)
{
    // get the cursor
    XFixesCursorImage *xcim = XFixesGetCursorImage(display);
    if(xcim == NULL)
        return;

    // calculate the position of the cursor
    int x = xcim->x - xcim->xhot - recording_area_x;
    int y = xcim->y - xcim->yhot - recording_area_y;

    // calculate the part of the cursor that's visible
    int cursor_left = std::max(0, -x), cursor_right = std::min((int) xcim->width, image.width() - x);
    int cursor_top = std::max(0, -y), cursor_bottom = std::min((int) xcim->height, image.height() - y);

    unsigned int pixel_bytes, r_offset, g_offset, b_offset; // ARGB
    pixel_bytes = 4;
    r_offset = 2; g_offset = 1; b_offset = 0;

    // draw the cursor
    // XFixesCursorImage uses 'long' instead of 'int' to store the cursor images, which is a bit weird since
    // 'long' is 64-bit on 64-bit systems and only 32 bits are actually used. The image uses premultiplied alpha.
    for(int j = cursor_top; j < cursor_bottom; ++j) {
        unsigned long *cursor_row = xcim->pixels + xcim->width * j;
        uint8_t *image_row = (uint8_t*) image.bits() + image.bytesPerLine() * (y + j);
        for(int i = cursor_left; i < cursor_right; ++i) {
            unsigned long cursor_pixel = cursor_row[i];
            uint8_t *image_pixel = image_row + pixel_bytes * (x + i);
            int cursor_a = (uint8_t) (cursor_pixel >> 24);
            int cursor_r = (uint8_t) (cursor_pixel >> 16);
            int cursor_g = (uint8_t) (cursor_pixel >> 8);
            int cursor_b = (uint8_t) (cursor_pixel >> 0);
            if(cursor_a == 255) {
                image_pixel[r_offset] = cursor_r;
                image_pixel[g_offset] = cursor_g;
                image_pixel[b_offset] = cursor_b;
            } else {
                image_pixel[r_offset] = (image_pixel[r_offset] * (255 - cursor_a) + 127) / 255 + cursor_r;
                image_pixel[g_offset] = (image_pixel[g_offset] * (255 - cursor_a) + 127) / 255 + cursor_g;
                image_pixel[b_offset] = (image_pixel[b_offset] * (255 - cursor_a) + 127) / 255 + cursor_b;
            }
        }
    }
    // free the cursor
    XFree(xcim);
}
#endif

XYGifFrame::XYGifFrame(QWidget *parent)
    : XYMovableWidget(parent),
      mStartResize(false),
      ui(new Ui::XYGifFrame)
{
    ui->setupUi(this);
    mGifCreator = new XYGifCreator(this);
    mTimer.setSingleShot(false);
    setWindowFlags( Qt::WindowStaysOnTopHint);

    ui->width->installEventFilter(this);
    ui->height->installEventFilter(this);
    connect(ui->width, SIGNAL(editingFinished()), this, SLOT(doResize()));
    connect(ui->height, SIGNAL(editingFinished()), this, SLOT(doResize()));
    connect(ui->gif, SIGNAL(clicked()), this, SLOT(active()));
    connect(ui->img, SIGNAL(clicked()), this, SLOT(packImages()));
    connect(&mTimer, SIGNAL(timeout()), this, SLOT(frame()));
    connect(ui->save, SIGNAL(clicked()), this, SLOT(setGifFile()));
    connect(mGifCreator, &XYGifCreator::finished, this, [this](){
        this->ui->tips->setText(QStringLiteral("保存Gif完成!"));
    });

    ui->content->adjustSize();
    setMouseTracking(true);
    setMinimumSize(162, 150);
    ui->gif->setFocus();
    m_tray = new QSystemTrayIcon(this);//实例化
    QPixmap m_logo(":/gif.ico");
    m_tray->setIcon(QIcon(m_logo));//设置图标
    m_tray->show();
    connect(m_tray,&QSystemTrayIcon::activated,this,&XYGifFrame::TrayIconAction);
    m_menu = new QMenu(this);
    m_resetAction = new QAction(this);
    m_resetAction->setText("show");
    m_quitAction = new QAction(this);
    m_resetAction->setIcon(QIcon(m_logo));
    m_quitAction->setText("quit");
    m_quitAction->setIcon(QIcon(m_logo));
    connect(m_quitAction,&QAction::triggered,qApp,&QApplication::quit);
    connect(m_resetAction,&QAction::triggered,this,&XYGifFrame::restory);
//    connect(m_closeDialog,&closeDialog::traySiganal,this,&XYGifFrame::Tray);
    m_tray->setContextMenu(m_menu);//设置托盘菜单
    m_menu->addAction(m_resetAction);
    m_menu->addAction(m_quitAction);

}

XYGifFrame::~XYGifFrame()
{
    delete ui;
}

void XYGifFrame::doResize()
{
    QRect rect(pos(), QSize(ui->width->value(), ui->height->value()));
    rect.adjust(-3, -3, 3, ui->content->height() + 5);

    resize(rect.size());
}

void XYGifFrame::packImages()
{
    XYPackImage img(this);
    img.exec();
}

void XYGifFrame::setGifFile()
{
    mGifFile = QFileDialog::getSaveFileName(this, "", QString("xy-%1.gif").arg(QDateTime::currentDateTime().toString("yyyyMMdd-hhmmss")));

    ui->save->setToolTip(mGifFile);
}

void XYGifFrame::active()
{
    if (!mTimer.isActive())
    {
        if (mGifCreator->isRunning())
        {
            QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("正在保存Gif,请稍等!"));
            return;
        }
        start();
    }
    else
    {
        stop();
    }
}

void XYGifFrame::start()
{
    if (!mGifFile.isEmpty())
    {
        mGifCreator->startThread();

        mGifCreator->begin(mGifFile.toUtf8().data(), ui->width->value(), ui->height->value(), 1);

        mPixs = 0;
        ui->gif->setText(QStringLiteral("停止录制"));

        frame();
        mTimer.start(static_cast<int>(1000.0 / ui->interval->value()));
        ui->width->setDisabled(true);
        ui->height->setDisabled(true);
        ui->interval->setDisabled(true);
    }
    else
    {
        QMessageBox::warning(this, QStringLiteral("提醒"), QStringLiteral("请先设置保存gif的位置!"));
    }
}

void XYGifFrame::stop()
{
    mTimer.stop();
    ui->gif->setText(QStringLiteral("开始录制"));
    mGifCreator->end();

    ui->width->setEnabled(true);
    ui->height->setEnabled(true);
    ui->interval->setEnabled(true);
    this->ui->tips->setText(QStringLiteral("请等待保存Gif..."));
}

void XYGifFrame::frame()
{
    QImage img = getCurScreenImage();
    if (!img.isNull())
    {
        mGifCreator->frame(img, ui->interval->value());
        mPixs++;

        ui->tips->setText(QStringLiteral("已保存 %1 张图片").arg(mPixs));
    }
}

bool XYGifFrame::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == ui->width
            || watched == ui->height)
    {
        if (event->type() == QEvent::Wheel)
        {
            doResize();
        }
    }
    return XYMovableWidget::eventFilter(watched, event);
}

void XYGifFrame::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.fillRect(rect(), Qt::gray);
}

void XYGifFrame::resizeEvent(QResizeEvent *)
{
    QRect rect = this->rect();
    rect.adjust(3, 3, -3, -(ui->content->height()  + 5));
    mRecordRect = rect;

    ui->width->setValue(mRecordRect.width());
    ui->height->setValue(mRecordRect.height());

    QRegion region(this->rect());
    setMask(region.xored(mRecordRect));
    qDebug()<<region.xored(mRecordRect);
    ui->content->move(width() - ui->content->width() - 3, height() - ui->content->height() - 3);
}

void XYGifFrame::mousePressEvent(QMouseEvent *event)
{
    QRect rect(QPoint(width() - 3, height() - 3), QSize(3, 3));
    if (rect.contains(event->pos()) && !mTimer.isActive())
    {
        mStartResize = true;
        mStartGeometry = QRect(event->globalPos(), size());
    }
    else
    {
        XYMovableWidget::mousePressEvent(event);
    }
}

void XYGifFrame::mouseReleaseEvent(QMouseEvent *event)
{
    mStartResize = false;
    setCursor(Qt::ArrowCursor);
    XYMovableWidget::mouseReleaseEvent(event);
}

void XYGifFrame::mouseMoveEvent(QMouseEvent *event)
{
    QRect rect(QPoint(width() - 3, height() - 3), QSize(3, 3));

    if (mStartResize)
    {
        QPoint ch = event->globalPos() - mStartGeometry.topLeft();
        resize(mStartGeometry.size() + QSize(ch.x(), ch.y()));
    }
    else if (rect.contains(event->pos()) && !mTimer.isActive())
    {
        setCursor(Qt::SizeFDiagCursor);
    }
    else
    {
        setCursor(Qt::ArrowCursor);
        XYMovableWidget::mouseMoveEvent(event);
    }
}

void XYGifFrame::wheelEvent(QWheelEvent *event)
{
    if (event->delta() < 0)
    {
        ui->content->move(ui->content->x() + 6, ui->content->y());
    }
    else
    {
        ui->content->move(ui->content->x() - 6, ui->content->y());
    }

    XYMovableWidget::wheelEvent(event);
}

QImage XYGifFrame::getCurScreenImage()
{
    QImage img;
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
    auto screen = qApp->screenAt(pos());
#else
    QScreen *screen = nullptr;
    foreach (screen, qApp->screens())
    {
        if (screen->geometry().contains(pos()))
        {
            break;
        }
    }
#endif
    if (screen != nullptr)
    {
#ifdef Q_OS_WIN32
        img = getScreenImage(x() + mRecordRect.x(),
                                    y() + mRecordRect.y(),
                                    mRecordRect.width(),
                                    mRecordRect.height());
#else
        img = screen->grabWindow(0,
                                        x() + mRecordRect.x(),
                                        y() + mRecordRect.y(),
                                        mRecordRect.width(),
                                        mRecordRect.height()).toImage();

        // 需要系统是x11后端
        auto display = XOpenDisplay(NULL);
        X11drawCursor(display, img, x() + mRecordRect.x(), y() + mRecordRect.y());
        XCloseDisplay(display);
#endif
    }

    return img;
}

void XYGifFrame::on_quit_clicked()
{
    this->hide();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值