Qt之屏幕录制设计(十六)

Qt开发 系列文章 - screencap(十六)


目录

前言

一、实现原理

二、实现方式

1.创建录屏窗口

2.录屏窗口类定义

3.自建容器对象定义

4.用户使用

5.效果演示

总结


前言

利用Qt实现屏幕录制设计,可以通过使用Qt自带的类QScreen、QPixmap、QImage来完成一帧图像的抓取,然后自建一个容器对象Gif和缓存变量GifWriter,来存放抓取的一帧帧图像。本篇文章将使用QT来实现一个简易的屏幕录制功能,下面就让我们一起来实现这个录屏功能吧。


一、实现原理

通过Qt自带的类QScreen、QPixmap、QImage完成一帧图像的抓取,首先是通过QScreen获取Windows显示的屏幕,然后将抓取屏幕的QRect(x位置,y位置,宽度,高度)窗口尺寸内容而构造的像素图返回给QPixmap,然后QPixmap将此素图信息按要求的图像格式(本文使用的是Format_RGBA8888像素格式)转换为QImage记录下来。

上一步完成将window桌面上选定的屏幕以图像的方式记录下来,这一步将QImage记录的信息以设定的帧率(即存储速度)保存到自建的容器对象Gif和缓存变量GifWriter中。其中帧率设定可以通过定时器来完成,创建一个按帧率fps来定时的定时器保存QImage在GifWriter中。具体实现方式见下文。

二、实现方式

1.创建录屏窗口

通过代码自建一个需要屏幕录制的窗口GifWidget,大小可以自己定义,代码如下(示例)。

void GifWidget::initControl()
{this->setObjectName("GifWidget");this->resize(800, 600);this->setSizeGripEnabled(true);QVBoxLayout *verticalLayout = new QVBoxLayout(this);verticalLayout->setSpacing(0);verticalLayout->setContentsMargins(11, 11, 11, 11);verticalLayout->setObjectName("verticalLayout");verticalLayout->setContentsMargins(0, 0, 0, 0);widgetTop = new QWidget(this);widgetTop->setObjectName("widgetTop");widgetTop->setMinimumSize(QSize(0, 35));widgetTop->setMaximumSize(QSize(16777215, 35));QHBoxLayout *layoutTop = new QHBoxLayout(widgetTop);layoutTop->setSpacing(0);layoutTop->setContentsMargins(11, 11, 11, 11);layoutTop->setObjectName("layoutTop");layoutTop->setContentsMargins(0, 0, 0, 0);QPushButton *btnIcon = new QPushButton(widgetTop);btnIcon->setObjectName("btnIcon");QSizePolicy sizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);sizePolicy.setHorizontalStretch(0);sizePolicy.setVerticalStretch(0);sizePolicy.setHeightForWidth(btnIcon->sizePolicy().hasHeightForWidth());btnIcon->setSizePolicy(sizePolicy);btnIcon->setMinimumSize(QSize(35, 0));btnIcon->setFlat(true);layoutTop->addWidget(btnIcon);QLabel *labTitle = new QLabel(widgetTop);labTitle->setObjectName("labTitle");layoutTop->addWidget(labTitle);QSpacerItem *horizontalSpacer = new QSpacerItem(87, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);layoutTop->addItem(horizontalSpacer);//最小窗口QPushButton *btnMinWin = new QPushButton(widgetTop);btnMinWin->setObjectName("btnMinWin");sizePolicy.setHeightForWidth(btnMinWin->sizePolicy().hasHeightForWidth());btnMinWin->setSizePolicy(sizePolicy);btnMinWin->setMinimumSize(QSize(35, 0));btnMinWin->setFocusPolicy(Qt::NoFocus);btnMinWin->setFlat(true);layoutTop->addWidget(btnMinWin);//最大窗口btnMaxWin = new QPushButton(widgetTop);btnMaxWin->setObjectName("btnMaxWin");sizePolicy.setHeightForWidth(btnMaxWin->sizePolicy().hasHeightForWidth());btnMaxWin->setSizePolicy(sizePolicy);btnMaxWin->setMinimumSize(QSize(35, 0));btnMaxWin->setFocusPolicy(Qt::NoFocus);btnMaxWin->setFlat(true);layoutTop->addWidget(btnMaxWin);//关闭窗口QPushButton *btnClose = new QPushButton(widgetTop);btnClose->setObjectName("btnClose");sizePolicy.setHeightForWidth(btnClose->sizePolicy().hasHeightForWidth());btnClose->setSizePolicy(sizePolicy);btnClose->setMinimumSize(QSize(35, 0));btnClose->setFocusPolicy(Qt::NoFocus);btnClose->setFlat(true);layoutTop->addWidget(btnClose);verticalLayout->addWidget(widgetTop);widgetMain = new QWidget(this);widgetMain->setObjectName("widgetMain");QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Expanding);sizePolicy1.setHorizontalStretch(0);sizePolicy1.setVerticalStretch(0);sizePolicy1.setHeightForWidth(widgetMain->sizePolicy().hasHeightForWidth());widgetMain->setSizePolicy(sizePolicy1);verticalLayout->addWidget(widgetMain);widgetBottom = new QWidget(this);widgetBottom->setObjectName("widgetBottom");widgetBottom->setMinimumSize(QSize(0, 45));widgetBottom->setMaximumSize(QSize(16777215, 45));QHBoxLayout *layoutBottom = new QHBoxLayout(widgetBottom);layoutBottom->setSpacing(6);layoutBottom->setContentsMargins(11, 11, 11, 11);layoutBottom->setObjectName("layoutBottom");layoutBottom->setContentsMargins(9, 9, -1, -1);QLabel *labFps = new QLabel(widgetBottom);labFps->setObjectName("labFps");layoutBottom->addWidget(labFps);txtFps = new QLineEdit(widgetBottom);txtFps->setObjectName("txtFps");txtFps->setMaximumSize(QSize(50, 16777215));txtFps->setAlignment(Qt::AlignCenter);layoutBottom->addWidget(txtFps);QLabel *labWidth = new QLabel(widgetBottom);labWidth->setObjectName("labWidth");layoutBottom->addWidget(labWidth);txtWidth = new QLineEdit(widgetBottom);txtWidth->setObjectName("txtWidth");txtWidth->setEnabled(true);txtWidth->setMaximumSize(QSize(50, 16777215));txtWidth->setAlignment(Qt::AlignCenter);layoutBottom->addWidget(txtWidth);QLabel *labHeight = new QLabel(widgetBottom);labHeight->setObjectName("labHeight");layoutBottom->addWidget(labHeight);txtHeight = new QLineEdit(widgetBottom);txtHeight->setObjectName("txtHeight");txtHeight->setEnabled(true);txtHeight->setMaximumSize(QSize(50, 16777215));txtHeight->setAlignment(Qt::AlignCenter);layoutBottom->addWidget(txtHeight);labStatus = new QLabel(widgetBottom);labStatus->setObjectName("labStatus");QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Preferred);sizePolicy2.setHorizontalStretch(0);sizePolicy2.setVerticalStretch(0);sizePolicy2.setHeightForWidth(labStatus->sizePolicy().hasHeightForWidth());labStatus->setSizePolicy(sizePolicy2);labStatus->setAlignment(Qt::AlignCenter);layoutBottom->addWidget(labStatus);btnStart = new QPushButton(widgetBottom);btnStart->setObjectName("btnStart");sizePolicy.setHeightForWidth(btnStart->sizePolicy().hasHeightForWidth());btnStart->setSizePolicy(sizePolicy);layoutBottom->addWidget(btnStart);verticalLayout->addWidget(widgetBottom);labTitle->setText("录屏工具");labFps->setText("帧率");labWidth->setText("宽度");labHeight->setText("高度");btnStart->setText("开始");this->setWindowTitle(labTitle->text());btnIcon->setIcon(style()->standardIcon(QStyle::SP_ComputerIcon));         // 电脑计算机图标btnMinWin->setIcon(style()->standardIcon(QStyle::SP_TitleBarMinButton));  // 最小窗口图标btnMaxWin->setIcon(style()->standardIcon(QStyle::SP_TitleBarMaxButton));  // 最大窗口图标btnClose->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton)); // 关闭窗口图标connect(btnMinWin, SIGNAL(clicked(bool)), this, SLOT(MinWin()));connect(btnMaxWin, SIGNAL(clicked(bool)), this, SLOT(MaxWin()));connect(btnClose, SIGNAL(clicked(bool)), this, SLOT(closeAll()));connect(btnStart, SIGNAL(clicked(bool)), this, SLOT(record()));connect(txtWidth, SIGNAL(editingFinished()), this, SLOT(resizeForm()));connect(txtHeight, SIGNAL(editingFinished()), this, SLOT(resizeForm()));
}
void GifWidget::initForm()
{borderWidth = 3;//bgColor = QColor(34, 163, 169);bgColor = QColor(255, 163, 169);fps = 10;txtFps->setText(QString::number(fps));gifWriter = 0;timer = new QTimer(this);timer->setInterval(100);connect(timer, SIGNAL(timeout()), this, SLOT(saveImage()));this->setAttribute(Qt::WA_TranslucentBackground);this->setWindowFlags(Qt::FramelessWindowHint);this->installEventFilter(this);QStringList qss;qss.append("QLabel{color:#ffff00;}");qss.append("#btnClose,#btnIcon,btnMinWin,#btnMaxWin{border:none;border-radius:0px;}");qss.append("#btnClose :hover{background-color:#ff0000;}");qss.append("#btnMinWin:hover{background-color:#1fab89;}");qss.append("#btnMaxWin:hover{background-color:#1fab89;}");qss.append("#btnClose,btnMinWin,#btnMaxWin{border-top-right-radius:5px;}");qss.append("#labTitle{font:bold 16px;}");qss.append("#labStatus{font:15px;}");this->setStyleSheet(qss.join(""));
}

这段代码完成了自定义屏幕录制窗口的功能、大小、样式,UI显示如下。

2.录屏窗口类定义

上一步完成屏幕录制窗口的功能、大小、样式设计,这一步将给出该录屏窗口对象类GifWidget的定义,并将自建的容器对象Gif和缓存变量GifWriter作为其私有变量,代码如下(示例)。

#include <QDialog>
#include "gif.h"class QLineEdit;
class QLabel;
class GifWidget : public QDialog
{Q_OBJECTQ_PROPERTY(int borderWidth READ getBorderWidth WRITE setBorderWidth)Q_PROPERTY(QColor bgColor READ getBgColor WRITE setBgColor)
public:static GifWidget *Instance();explicit GifWidget(QWidget *parent = 0);
protected:bool eventFilter(QObject *watched, QEvent *event);void resizeEvent(QResizeEvent *);void paintEvent(QPaintEvent *);
private:static QScopedPointer<GifWidget> self;QWidget *widgetTop;         //标题栏QWidget *widgetMain;        //中间部分QWidget *widgetBottom;      //底部栏QLineEdit *txtFps;          //帧率输入框QLineEdit *txtWidth;        //宽度输入框QLineEdit *txtHeight;       //高度输入框QPushButton *btnMaxWin;     //最大化窗口QPushButton *btnStart;      //开始按钮QLabel *labStatus;          //显示状态信息int fps;                    //帧数 100为1sint borderWidth;            //边框宽度QColor bgColor;             //背景颜色int count;                  //帧数计数QString fileName;           //保存文件名称QRect rectGif;              //截屏区域QTimer *timer;              //截屏定时器Gif gif;                    //gif类对象Gif::GifWriter *gifWriter;  //gif写入对象
public:int getBorderWidth()        const;QColor getBgColor()         const;
private slots:void initControl();void initForm();void saveImage();void record();void MinWin();void MaxWin();void closeAll();void resizeForm();
public Q_SLOTS:void setBorderWidth(int borderWidth);void setBgColor(const QColor &bgColor);
};

3.自建容器对象定义

这一步将完成自建容器对象Gif和缓存变量GifWriter的定义,代码如下(示例):

#ifndef __gif_h__
#define __gif_h__#include <stdio.h>   // for FILE*
#include <string.h>  // for memcpy and bzero
#include <qdebug.h>
#include <stdint.h>  // for integer typedefs#ifndef GIF_TEMP_MALLOC
#include <stdlib.h>
#define GIF_TEMP_MALLOC malloc
#endif#ifndef GIF_TEMP_FREE
#include <stdlib.h>
#define GIF_TEMP_FREE free
#endif#ifndef GIF_MALLOC
#include <stdlib.h>
#define GIF_MALLOC malloc
#endif#ifndef GIF_FREE
#include <stdlib.h>
#define GIF_FREE free
#endifclass Gif
{
public:int kGifTransIndex;struct GifPalette {int bitDepth;uint8_t r[256];uint8_t g[256];uint8_t b[256];uint8_t treeSplitElt[255];uint8_t treeSplit[255];};// max, min, and abs functionsint GifIMax(int l, int r) {return l > r ? l : r;}int GifIMin(int l, int r) {return l < r ? l : r;}int GifIAbs(int i) {return i < 0 ? -i : i;}void GifGetClosestPaletteColor(GifPalette *pPal, int r, int g, int b, int &bestInd, int &bestDiff, int treeRoot = 1) {// base case, reached the bottom of the treeif (treeRoot > (1 << pPal->bitDepth) - 1) {int ind = treeRoot - (1 << pPal->bitDepth);if (ind == kGifTransIndex) {return;}// check whether this color is better than the current winnerint r_err = r - ((int32_t)pPal->r[ind]);int g_err = g - ((int32_t)pPal->g[ind]);int b_err = b - ((int32_t)pPal->b[ind]);int diff = GifIAbs(r_err) + GifIAbs(g_err) + GifIAbs(b_err);if (diff < bestDiff) {bestInd = ind;bestDiff = diff;}return;}// take the appropriate color (r, g, or b) for this node of the k-d treeint comps[3];comps[0] = r;comps[1] = g;comps[2] = b;int splitComp = comps[pPal->treeSplitElt[treeRoot]];int splitPos = pPal->treeSplit[treeRoot];if (splitPos > splitComp) {// check the left subtreeGifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2);if (bestDiff > splitPos - splitComp) {// cannot prove there's not a better value in the right subtree, check that tooGifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1);}} else {GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2 + 1);if (bestDiff > splitComp - splitPos) {GifGetClosestPaletteColor(pPal, r, g, b, bestInd, bestDiff, treeRoot * 2);}}}void GifSwapPixels(uint8_t *image, int pixA, int pixB) {uint8_t rA = image[pixA * 4];uint8_t gA = image[pixA * 4 + 1];uint8_t bA = image[pixA * 4 + 2];uint8_t aA = image[pixA * 4 + 3];uint8_t rB = image[pixB * 4];uint8_t gB = image[pixB * 4 + 1];uint8_t bB = image[pixB * 4 + 2];uint8_t aB = image[pixA * 4 + 3];image[pixA * 4] = rB;image[pixA * 4 + 1] = gB;image[pixA * 4 + 2] = bB;image[pixA * 4 + 3] = aB;image[pixB * 4] = rA;image[pixB * 4 + 1] = gA;image[pixB * 4 + 2] = bA;image[pixB * 4 + 3] = aA;}// just the partition operation from quicksortint GifPartition(uint8_t *image, const int left, const int right, const int elt, int pivotIndex) {const int pivotValue = image[(pivotIndex) * 4 + elt];GifSwapPixels(image, pivotIndex, right - 1);int storeIndex = left;bool split = 0;for (int ii = left; ii < right - 1; ++ii) {int arrayVal = image[ii * 4 + elt];if (arrayVal < pivotValue) {GifSwapPixels(image, ii, storeIndex);++storeIndex;} else if (arrayVal == pivotValue) {if (split) {GifSwapPixels(image, ii, storeIndex);++storeIndex;}split = !split;}}GifSwapPixels(image, storeIndex, right - 1);return storeIndex;}// Perform an incomplete sort, finding all elements above and below the desired medianvoid GifPartitionByMedian(uint8_t *image, int left, int right, int com, int neededCenter) {if (left < right - 1) {int pivotIndex = left + (right - left) / 2;pivotIndex = GifPartition(image, left, right, com, pivotIndex);// Only "sort" the section of the array that contains the medianif (pivotIndex > neededCenter) {GifPartitionByMedian(image, left, pivotIndex, com, neededCenter);}if (pivotIndex < neededCenter) {GifPartitionByMedian(image, pivotIndex + 1, right, com, neededCenter);}}}// Builds a palette by creating a balanced k-d tree of all pixels in the imagevoid GifSplitPalette(uint8_t *image,int numPixels, int firstElt,int lastElt, int splitElt,int splitDist, int treeNode,bool buildForDither, GifPalette *pal) {if (lastElt <= firstElt || numPixels == 0) {return;}// base case, bottom of the treeif (lastElt == firstElt + 1) {if (buildForDither) {// Dithering needs at least one color as dark as anything// in the image and at least one brightest color -// otherwise it builds up error and produces strange artifactsif (firstElt == 1) {// special case: the darkest color in the imageuint32_t r = 255, g = 255, b = 255;for (int ii = 0; ii < numPixels; ++ii) {r = (uint32_t)GifIMin((int32_t)r, image[ii * 4 + 0]);g = (uint32_t)GifIMin((int32_t)g, image[ii * 4 + 1]);b = (uint32_t)GifIMin((int32_t)b, image[ii * 4 + 2]);}pal->r[firstElt] = (uint8_t)r;pal->g[firstElt] = (uint8_t)g;pal->b[firstElt] = (uint8_t)b;return;}if (firstElt == (1 << pal->bitDepth) - 1) {// special case: the lightest color in the imageuint32_t r = 0, g = 0, b = 0;for (int ii = 0; ii < numPixels; ++ii) {r = (uint32_t)GifIMax((int32_t)r, image[ii * 4 + 0]);g = (uint32_t)GifIMax((int32_t)g, image[ii * 4 + 1]);b = (uint32_t)GifIMax((int32_t)b, image[ii * 4 + 2]);}pal->r[firstElt] = (uint8_t)r;pal->g[firstElt] = (uint8_t)g;pal->b[firstElt] = (uint8_t)b;return;}}// otherwise, take the average of all colors in this subcubeuint64_t r = 0, g = 0, b = 0;for (int ii = 0; ii < numPixels; ++ii) {r += image[ii * 4 + 0];g += image[ii * 4 + 1];b += image[ii * 4 + 2];}r += (uint64_t)numPixels / 2;  // round to nearestg += (uint64_t)numPixels / 2;b += (uint64_t)numPixels / 2;r /= (uint64_t)numPixels;g /= (uint64_t)numPixels;b /= (uint64_t)numPixels;pal->r[firstElt] = (uint8_t)r;pal->g[firstElt] = (uint8_t)g;pal->b[firstElt] = (uint8_t)b;return;}// Find the axis with the largest rangeint minR = 255, maxR = 0;int minG = 255, maxG = 0;int minB = 255, maxB = 0;for (int ii = 0; ii < numPixels; ++ii) {int r = image[ii * 4 + 0];int g = image[ii * 4 + 1];int b = image[ii * 4 + 2];if (r > maxR) {maxR = r;}if (r < minR) {minR = r;}if (g > maxG) {maxG = g;}if (g < minG) {minG = g;}if (b > maxB) {maxB = b;}if (b < minB) {minB = b;}}int rRange = maxR - minR;int gRange = maxG - minG;int bRange = maxB - minB;// and split along that axis. (incidentally, this means this isn't a "proper" k-d tree but I don't know what else to call it)int splitCom = 1;if (bRange > gRange) {splitCom = 2;}if (rRange > bRange && rRange > gRange) {splitCom = 0;}int subPixelsA = numPixels * (splitElt - firstElt) / (lastElt - firstElt);int subPixelsB = numPixels - subPixelsA;GifPartitionByMedian(image, 0, numPixels, splitCom, subPixelsA);pal->treeSplitElt[treeNode] = (uint8_t)splitCom;pal->treeSplit[treeNode] = image[subPixelsA * 4 + splitCom];GifSplitPalette(image,              subPixelsA, firstElt, splitElt, splitElt - splitDist, splitDist / 2, treeNode * 2,   buildForDither, pal);GifSplitPalette(image + subPixelsA * 4, subPixelsB, splitElt, lastElt,  splitElt + splitDist, splitDist / 2, treeNode * 2 + 1, buildForDither, pal);}int GifPickChangedPixels(const uint8_t *lastFrame, uint8_t *frame, int numPixels) {int numChanged = 0;uint8_t *writeIter = frame;for (int ii = 0; ii < numPixels; ++ii) {if (lastFrame[0] != frame[0] ||lastFrame[1] != frame[1] ||lastFrame[2] != frame[2]) {writeIter[0] = frame[0];writeIter[1] = frame[1];writeIter[2] = frame[2];++numChanged;writeIter += 4;}lastFrame += 4;frame += 4;}return numChanged;}void GifMakePalette(const uint8_t *lastFrame,const uint8_t *nextFrame,uint32_t width, uint32_t height,int bitDepth, bool buildForDither,GifPalette *pPal) {pPal->bitDepth = bitDepth;size_t imageSize = (size_t)(width * height * 4 * sizeof(uint8_t));uint8_t *destroyableImage = (uint8_t *)GIF_TEMP_MALLOC(imageSize);memcpy(destroyableImage, nextFrame, imageSize);int numPixels = (int)(width * height);if (lastFrame) {numPixels = GifPickChangedPixels(lastFrame, destroyableImage, numPixels);}const int lastElt = 1 << bitDepth;const int splitElt = lastElt / 2;const int splitDist = splitElt / 2;GifSplitPalette(destroyableImage, numPixels, 1, lastElt, splitElt, splitDist, 1, buildForDither, pPal);GIF_TEMP_FREE(destroyableImage);// add the bottom node for the transparency indexpPal->treeSplit[1 << (bitDepth - 1)] = 0;pPal->treeSplitElt[1 << (bitDepth - 1)] = 0;pPal->r[0] = pPal->g[0] = pPal->b[0] = 0;}// Implements Floyd-Steinberg dithering, writes palette value to alphavoid GifDitherImage(const uint8_t *lastFrame, const uint8_t *nextFrame,uint8_t *outFrame, uint32_t width,uint32_t height, GifPalette *pPal) {int numPixels = (int)(width * height);int32_t *quantPixels = (int32_t *)GIF_TEMP_MALLOC(sizeof(int32_t) * (size_t)numPixels * 4);for (int ii = 0; ii < numPixels * 4; ++ii) {uint8_t pix = nextFrame[ii];int32_t pix16 = int32_t(pix) * 256;quantPixels[ii] = pix16;}for (uint32_t yy = 0; yy < height; ++yy) {for (uint32_t xx = 0; xx < width; ++xx) {int32_t *nextPix = quantPixels + 4 * (yy * width + xx);const uint8_t *lastPix = lastFrame ? lastFrame + 4 * (yy * width + xx) : NULL;// Compute the colors we want (rounding to nearest)int32_t rr = (nextPix[0] + 127) / 256;int32_t gg = (nextPix[1] + 127) / 256;int32_t bb = (nextPix[2] + 127) / 256;// if it happens that we want the color from last frame, then just write out// a transparent pixelif (lastFrame &&lastPix[0] == rr &&lastPix[1] == gg &&lastPix[2] == bb) {nextPix[0] = rr;nextPix[1] = gg;nextPix[2] = bb;nextPix[3] = kGifTransIndex;continue;}int32_t bestDiff = 1000000;int32_t bestInd = kGifTransIndex;// Search the paleteGifGetClosestPaletteColor(pPal, rr, gg, bb, bestInd, bestDiff);// Write the result to the temp bufferint32_t r_err = nextPix[0] - int32_t(pPal->r[bestInd]) * 256;int32_t g_err = nextPix[1] - int32_t(pPal->g[bestInd]) * 256;int32_t b_err = nextPix[2] - int32_t(pPal->b[bestInd]) * 256;nextPix[0] = pPal->r[bestInd];nextPix[1] = pPal->g[bestInd];nextPix[2] = pPal->b[bestInd];nextPix[3] = bestInd;// Propagate the error to the four adjacent locations// that we haven't touched yetint quantloc_7 = (int)(yy * width + xx + 1);int quantloc_3 = (int)(yy * width + width + xx - 1);int quantloc_5 = (int)(yy * width + width + xx);int quantloc_1 = (int)(yy * width + width + xx + 1);if (quantloc_7 < numPixels) {int32_t *pix7 = quantPixels + 4 * quantloc_7;pix7[0] += GifIMax(-pix7[0], r_err * 7 / 16);pix7[1] += GifIMax(-pix7[1], g_err * 7 / 16);pix7[2] += GifIMax(-pix7[2], b_err * 7 / 16);}if (quantloc_3 < numPixels) {int32_t *pix3 = quantPixels + 4 * quantloc_3;pix3[0] += GifIMax(-pix3[0], r_err * 3 / 16);pix3[1] += GifIMax(-pix3[1], g_err * 3 / 16);pix3[2] += GifIMax(-pix3[2], b_err * 3 / 16);}if (quantloc_5 < numPixels) {int32_t *pix5 = quantPixels + 4 * quantloc_5;pix5[0] += GifIMax(-pix5[0], r_err * 5 / 16);pix5[1] += GifIMax(-pix5[1], g_err * 5 / 16);pix5[2] += GifIMax(-pix5[2], b_err * 5 / 16);}if (quantloc_1 < numPixels) {int32_t *pix1 = quantPixels + 4 * quantloc_1;pix1[0] += GifIMax(-pix1[0], r_err / 16);pix1[1] += GifIMax(-pix1[1], g_err / 16);pix1[2] += GifIMax(-pix1[2], b_err / 16);}}}// Copy the palettized result to the output bufferfor (int ii = 0; ii < numPixels * 4; ++ii) {outFrame[ii] = (uint8_t)quantPixels[ii];}GIF_TEMP_FREE(quantPixels);}// Picks palette colors for the image using simple thresholding, no ditheringvoid GifThresholdImage(const uint8_t *lastFrame, const uint8_t *nextFrame,uint8_t *outFrame, uint32_t width, uint32_t height,GifPalette *pPal) {uint32_t numPixels = width * height;for (uint32_t ii = 0; ii < numPixels; ++ii) {// if a previous color is available, and it matches the current color,// set the pixel to transparentif (lastFrame &&lastFrame[0] == nextFrame[0] &&lastFrame[1] == nextFrame[1] &&lastFrame[2] == nextFrame[2]) {outFrame[0] = lastFrame[0];outFrame[1] = lastFrame[1];outFrame[2] = lastFrame[2];outFrame[3] = kGifTransIndex;} else {// palettize the pixelint32_t bestDiff = 1000000;int32_t bestInd = 1;GifGetClosestPaletteColor(pPal, nextFrame[0], nextFrame[1], nextFrame[2], bestInd, bestDiff);// Write the resulting color to the output bufferoutFrame[0] = pPal->r[bestInd];outFrame[1] = pPal->g[bestInd];outFrame[2] = pPal->b[bestInd];outFrame[3] = (uint8_t)bestInd;}if (lastFrame) {lastFrame += 4;}outFrame += 4;nextFrame += 4;}}struct GifBitStatus {uint8_t bitIndex;  // how many bits in the partial byte written so faruint8_t byte;      // current partial byteuint32_t chunkIndex;uint8_t chunk[256];   // bytes are written in here until we have 256 of them, then written to the file};// insert a single bitvoid GifWriteBit(GifBitStatus &stat, uint32_t bit) {bit = bit & 1;bit = bit << stat.bitIndex;stat.byte |= bit;++stat.bitIndex;if (stat.bitIndex > 7) {// move the newly-finished byte to the chunk bufferstat.chunk[stat.chunkIndex++] = stat.byte;// and start a new bytestat.bitIndex = 0;stat.byte = 0;}}// write all bytes so far to the filevoid GifWriteChunk(FILE *f, GifBitStatus &stat) {fputc((int)stat.chunkIndex, f);fwrite(stat.chunk, 1, stat.chunkIndex, f);stat.bitIndex = 0;stat.byte = 0;stat.chunkIndex = 0;}void GifWriteCode(FILE *f, GifBitStatus &stat, uint32_t code, uint32_t length) {for (uint32_t ii = 0; ii < length; ++ii) {GifWriteBit(stat, code);code = code >> 1;if (stat.chunkIndex == 255) {GifWriteChunk(f, stat);}}}struct GifLzwNode {uint16_t m_next[256];};// write a 256-color (8-bit) image palette to the filevoid GifWritePalette(const GifPalette *pPal, FILE *f) {fputc(0, f);  // first color: transparencyfputc(0, f);fputc(0, f);for (int ii = 1; ii < (1 << pPal->bitDepth); ++ii) {uint32_t r = pPal->r[ii];uint32_t g = pPal->g[ii];uint32_t b = pPal->b[ii];fputc((int)r, f);fputc((int)g, f);fputc((int)b, f);}}// write the image header, LZW-compress and write out the imagevoid GifWriteLzwImage(FILE *f, uint8_t *image, uint32_t left,uint32_t top,  uint32_t width,uint32_t height, uint32_t delay,GifPalette *pPal) {// graphics control extensionfputc(0x21, f);fputc(0xf9, f);fputc(0x04, f);fputc(0x05, f); // leave prev frame in place, this frame has transparencyfputc(delay & 0xff, f);fputc((delay >> 8) & 0xff, f);fputc(kGifTransIndex, f); // transparent color indexfputc(0, f);fputc(0x2c, f); // image descriptor blockfputc(left & 0xff, f);           // corner of image in canvas spacefputc((left >> 8) & 0xff, f);fputc(top & 0xff, f);fputc((top >> 8) & 0xff, f);fputc(width & 0xff, f);          // width and height of imagefputc((width >> 8) & 0xff, f);fputc(height & 0xff, f);fputc((height >> 8) & 0xff, f);//fputc(0, f); // no local color table, no transparency//fputc(0x80, f); // no local color table, but transparencyfputc(0x80 + pPal->bitDepth - 1, f); // local color table present, 2 ^ bitDepth entriesGifWritePalette(pPal, f);const int minCodeSize = pPal->bitDepth;const uint32_t clearCode = 1 << pPal->bitDepth;fputc(minCodeSize, f); // min code size 8 bitsGifLzwNode *codetree = (GifLzwNode *)GIF_TEMP_MALLOC(sizeof(GifLzwNode) * 4096);memset(codetree, 0, sizeof(GifLzwNode) * 4096);int32_t curCode = -1;uint32_t codeSize = (uint32_t)minCodeSize + 1;uint32_t maxCode = clearCode + 1;GifBitStatus stat;stat.byte = 0;stat.bitIndex = 0;stat.chunkIndex = 0;GifWriteCode(f, stat, clearCode, codeSize);  // start with a fresh LZW dictionaryfor (uint32_t yy = 0; yy < height; ++yy) {for (uint32_t xx = 0; xx < width; ++xx) {uint8_t nextValue = image[(yy * width + xx) * 4 + 3];// "loser mode" - no compression, every single code is followed immediately by a clear//WriteCode( f, stat, nextValue, codeSize );//WriteCode( f, stat, 256, codeSize );if (curCode < 0) {// first value in a new runcurCode = nextValue;} else if (codetree[curCode].m_next[nextValue]) {// current run already in the dictionarycurCode = codetree[curCode].m_next[nextValue];} else {// finish the current run, write a codeGifWriteCode(f, stat, (uint32_t)curCode, codeSize);// insert the new run into the dictionarycodetree[curCode].m_next[nextValue] = (uint16_t)++maxCode;if (maxCode >= (1ul << codeSize)) {// dictionary entry count has broken a size barrier,// we need more bits for codescodeSize++;}if (maxCode == 4095) {// the dictionary is full, clear it out and begin anewGifWriteCode(f, stat, clearCode, codeSize); // clear treememset(codetree, 0, sizeof(GifLzwNode) * 4096);codeSize = (uint32_t)(minCodeSize + 1);maxCode = clearCode + 1;}curCode = nextValue;}}}// compression footerGifWriteCode(f, stat, (uint32_t)curCode, codeSize);GifWriteCode(f, stat, clearCode, codeSize);GifWriteCode(f, stat, clearCode + 1, (uint32_t)minCodeSize + 1);// write out the last partial chunkwhile (stat.bitIndex) {GifWriteBit(stat, 0);}if (stat.chunkIndex) {GifWriteChunk(f, stat);}fputc(0, f); // image block terminatorGIF_TEMP_FREE(codetree);}struct GifWriter {FILE *f;uint8_t *oldImage;bool firstFrame;};bool GifBegin(GifWriter *writer, const char *filename,uint32_t width, uint32_t height,uint32_t delay, int32_t bitDepth = 8,bool dither = false) {(void)bitDepth;(void)dither; // Mute "Unused argument" warnings
#if defined(_MSC_VER) && (_MSC_VER >= 1400)writer->f = 0;fopen_s(&writer->f, filename, "wb");
#elsewriter->f = fopen(filename, "wb");
#endifif (!writer->f) {return false;}writer->firstFrame = true;// allocatewriter->oldImage = (uint8_t *)GIF_MALLOC(width * height * 4);fputs("GIF89a", writer->f);// screen descriptorfputc(width & 0xff, writer->f);fputc((width >> 8) & 0xff, writer->f);fputc(height & 0xff, writer->f);fputc((height >> 8) & 0xff, writer->f);fputc(0xf0, writer->f);  // there is an unsorted global color table of 2 entriesfputc(0, writer->f);     // background colorfputc(0, writer->f);     // pixels are square (we need to specify this because it's 1989)fputc(0, writer->f);fputc(0, writer->f);fputc(0, writer->f);// color 1: also blackfputc(0, writer->f);fputc(0, writer->f);fputc(0, writer->f);if (delay != 0) {// animation headerfputc(0x21, writer->f); // extensionfputc(0xff, writer->f); // application specificfputc(11, writer->f); // length 11fputs("NETSCAPE2.0", writer->f); // yes, reallyfputc(3, writer->f); // 3 bytes of NETSCAPE2.0 datafputc(1, writer->f); // JUST BECAUSEfputc(0, writer->f); // loop infinitely (byte 0)fputc(0, writer->f); // loop infinitely (byte 1)fputc(0, writer->f); // block terminator}return true;}bool GifWriteFrame(GifWriter *writer, const uint8_t *image,uint32_t width, uint32_t height,uint32_t delay, int bitDepth = 8, bool dither = false) {if (!writer->f) {return false;}const uint8_t *oldImage = writer->firstFrame ? NULL : writer->oldImage;writer->firstFrame = false;GifPalette pal;GifMakePalette((dither ? NULL : oldImage), image, width, height, bitDepth, dither, &pal);if (dither) {GifDitherImage(oldImage, image, writer->oldImage, width, height, &pal);} else {GifThresholdImage(oldImage, image, writer->oldImage, width, height, &pal);}GifWriteLzwImage(writer->f, writer->oldImage, 0, 0, width, height, delay, &pal);return true;}bool GifEnd(GifWriter *writer) {if (!writer->f) {return false;}fputc(0x3b, writer->f); // end of filefclose(writer->f);GIF_FREE(writer->oldImage);writer->f = NULL;writer->oldImage = NULL;return true;}
};
#endif

4.用户使用

创建完上面的GifWidget对象后,用户对象frmGifWidget需要调用/使用它,具体含义实现如下。

#include "frmgifwidget.h"
#include "ui_frmgifwidget.h"
#include "gifwidget.h"
frmGifWidget::frmGifWidget(QWidget *parent) : QWidget(parent), ui(new Ui::frmGifWidget)
{ui->setupUi(this);v = new vedioplayer;
}
void frmGifWidget::on_pushButton_clicked()
{if(ui->pushButton->text() == tr("录 屏")){//设置截图窗口置顶显示GifWidget::Instance()->setWindowFlags(GifWidget::Instance()->windowFlags() | Qt::WindowStaysOnTopHint);GifWidget::Instance()->show();ui->pushButton->setText(tr("隐 藏"));}else if(ui->pushButton->text() == tr("隐 藏")){ui->pushButton->setText(tr("录 屏"));GifWidget::Instance()->hide();}
}

5.效果演示

上述功能代码弄完后,编译运行,显示窗口如下。

  

在效果演示画面上,可以在视频软件界面看到,另一个视频播放功能,这个在上一章节Qt之简易音视频播放器设计(十五)-CSDN博客已经讲解说明。


总结

本文利用Qt设计了一款简易屏幕录制软件,点击即可使用,默认保存的GIF格式,但你也可以保存为其它格式。

博文中相应的工程代码Qt-Case.zip 利用Qt开发软件进行编的例程,为博文提供案例-CSDN文库。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/502573.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实时高保真人脸编辑方法PersonaMagic,可根据肖像无缝生成新角色、风格或场景图像。

今天给大家介绍的是一个高保真实时人脸编辑方法PersonaMagic&#xff0c;通过分阶段的文本条件调节和动态嵌入学习来优化人脸定制。该技术利用时序动态的交叉注意力机制&#xff0c;能够在不同阶段有效捕捉人脸特征&#xff0c;从而在生成个性化图像时最大程度地保留身份信息。…

我的创作纪念日——《惊变128天》

我的创作纪念日——《惊变128天》 机缘收获日常成就憧憬 机缘 时光飞逝&#xff0c;转眼间&#xff0c;我已在这条创作之路上走过了 128 天。回顾起 2024 年 8 月 29 日&#xff0c;我满怀忐忑与期待&#xff0c;撰写了第一篇技术博客《讲解LeetCode第1题&#xff1a;两数之和…

常见的框架漏洞复现

1.Thinkphp Thinkphp5x远程命令执行及getshell 搭建靶场 cd vulhub/thinkphp/5-rce docker-compose up -d 首页 漏洞根本源于 thinkphp/library/think/Request.php 中method方法可以进行变量覆盖&#xff0c;通过覆盖类的核心属性filter导致rce&#xff0c;其攻击点较为多&…

云备份项目--服务端编写

文章目录 7. 数据管理模块7.1 如何设计7.2 完整的类 8. 热点管理8.1 如何设计8.2 完整的类 9. 业务处理模块9.1 如何设计9.2 完整的类9.3 测试9.3.1 测试展示功能 完整的代码–gitee链接 7. 数据管理模块 TODO: 读写锁&#xff1f;普通锁&#xff1f; 7.1 如何设计 需要管理…

flutter在windows平台中运行报错

PS D:\F\luichun> flutter run当运行flutter项目时&#xff0c;【解决如下报错】 /C:/flutter/packages/flutter/lib/src/painting/star_border.dart:530:27: Error: The getter Matrix4 isnt defined for the class _StarGenerator.- _StarGenerator is from package:flut…

Synthesia技术浅析(二):虚拟人物视频生成

Synthesia 的虚拟人物视频生成模块是其核心技术之一&#xff0c;能够将文本输入转换为带有同步语音和口型的虚拟人物视频。该模块如下所示&#xff1a; 1.文本输入处理 2.语音生成&#xff08;TTS, Text-to-Speech&#xff09; 3.口型同步&#xff08;Lip Syncing&#xff0…

[Linux]进程间通信-共享内存与消息队列

目录 一、共享内存 1.共享内存的原理 2.共享内存的接口 命令行 创建共享内存 共享内存的挂接 去掉挂接 共享内存的控制 3.共享内存的使用代码 Comm.hpp--封装了操作接口 客户端--写入端 服务器--读取端 4.管道实现共享内存的同步机制 二、消息队列 1.底层原理 2…

凸包(convex hull)简述

凸包&#xff08;convex hull&#xff09;简述 这里主要介绍二维凸包&#xff0c;二维凸多边形是指所有内角都在 [ 0 , Π ] [0,\Pi ] [0,Π]范围内的简单多边形。 凸包是指在平面上包含所有给定点的最小凸多边形。 数学定义&#xff1a;对于给定集合 X X X&#xff0c;所有…

【ArcGISPro/GeoScenePro】检查多光谱影像的属性并优化其外观

数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 操作 其他数据 检查影像的属性 熟悉检查您正在使用的栅格属性非常重要。

提升汽车金融租赁系统的效率与风险管理策略探讨

内容概要 在汽车金融租赁系统这个复杂的生态中&#xff0c;提升整体效率是每个企业都渴望达成的目标。首先&#xff0c;优化业务流程是实现高效运行的基础。通过分析目前的流程&#xff0c;找出冗余环节并进行简化&#xff0c;能够帮助企业缩短审批时间&#xff0c;提高客户满…

以太网UDP协议栈实现(支持ARP、ICMP、UDP)--FPGA学习笔记26

纯verilog实现&#xff0c;仅使用锁相环IP、FIFO IP&#xff0c;方便跨平台移植。支持ping指令。 以太网系列文章&#xff1a; 以太网ICMP协议(ping指令)——FPGA学习笔记25-CSDN博客 以太网ARP协议——FPGA学习笔记23-CSDN博客 以太网PHY_MDIO通信&#xff08;基于RTL821…

edeg插件/扩展推荐:助力生活工作

WeTab 此插件在我看来有2个作用 1.改变edeg的主页布局和样式,使其更加精简,无广告 2.提供付费webtab Ai(底层是chatGpt) 沉浸式翻译 此插件可翻译网页的内容 假设我们浏览github 翻译前 翻译后 Better Ruler 可以对网页的距离进行测量 适合写前端的小伙伴 用法示例:

java项目之校园管理系统的设计与实现(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的校园管理系统的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; springboot校园…

设计模式 结构型 适配器模式(Adapter Pattern)与 常见技术框架应用 解析

适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口&#xff0c;从而使原本因接口不兼容而无法一起工作的类能够协同工作。这种设计模式在软件开发中非常有用&#xff0c;尤其是在需要集成…

打造三甲医院人工智能矩阵新引擎(一):文本大模型篇--基于GPT-4o的探索

一、引言 当今时代,人工智能技术正以前所未有的速度蓬勃发展,深刻且广泛地渗透至各个领域,医疗行业更是这场变革的前沿阵地。在人口老龄化加剧、慢性疾病患病率上升以及人们对健康需求日益增长的大背景下,三甲医院作为医疗体系的核心力量,承担着极为繁重且复杂的医疗任务。…

S7-200采集频率信号

S7-200可以借助高速计数器完成频率信号采集&#xff0c;接入流量计、转速等信号。官方给出的程序块无法完成多路同时采集&#xff0c;需要自己进行修改。 首先下载官方的频率采集库 SIOS 下载后导入library&#xff0c;在library中出现Frequency(v1.0) 拖进ladder后&#xf…

专家混合(MoE)大语言模型:免费的嵌入模型新宠

专家混合&#xff08;MoE&#xff09;大语言模型&#xff1a;免费的嵌入模型新宠 今天&#xff0c;我们深入探讨一种备受瞩目的架构——专家混合&#xff08;Mixture-of-Experts&#xff0c;MoE&#xff09;大语言模型&#xff0c;它在嵌入模型领域展现出了独特的魅力。 一、M…

【Vue】分享一个快速入门的前端框架以及如何搭建

先上效果图: 登录 菜单: 下载地址: 链接&#xff1a;https://pan.baidu.com/s/1m-ZlBARWU6_2n8jZil_RAQ 提取码&#xff1a;ui20 … 主要是可以自定义设置token,更改后端请求地址较为方便。 应用设置: 登录与token设置: 在这里设置不用登录,可以请求的接口: request.js i…

MySQL叶子节点为啥使用双向链表?不使用单向呢?

文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ 文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ 文章内容收录到个人网站&#xff0c;方便阅读&#xff1a;http://hardyfish.top/ MySQL 中的 B 树索引&#x…

用户界面的UML建模10

非正常的可视反馈可伴随着同步事件发生&#xff0c;而同步事件可由系统动作产生。但是&#xff0c;可以分别对它们进行建模。 在下节中将对这些特殊的事件依次进行论述。 6.1 异常处理建模 异常&#xff0c;由Meyer 定义[16],其作为运行时事件&#xff08;run-time events&a…