目录
前言
一、实现原理
二、实现方式
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文库。