C#调用C++截屏及图片转JPEG

前言

C#实现同屏的时候,频繁截屏内存并不能很好的释放,所以就打算用C++实现这部分的功能。

这里图片的压缩用到了JpegLib, JpegLib是一个用C编写的jpeg图像压缩免费库,许多应用程序对jepg的支持都依赖于该库。

编译库

下载

下载地址:https://www.ijg.org/

官方下载地址:jpegsr9d.zip

百度云链接:https://pan.baidu.com/s/13xhEjxWR9b6Bx7OL6tLTIw
提取码:6owy

编译

使用Vistual studio的命令行工具进入源码文件夹

如图

image-20210708142807989

注意

不要用系统的CMD或者Powershell,无法编译。

源码中复制一份jconfig.vc,改变后缀变为jconfig.h

执行如下命令

1
nmake -f makefile.vc

提示找不到win32.mak文件,在C盘搜索这个文件名,没有发现该文件,可以直接下一个

链接:https://pan.baidu.com/s/1y-uAsb5e6KdWFRgY3MxaKg
提取码:kn30
把·win32.mak放在源码目录下即可(Win10环境下需要下载)

编译成功后再项目下创建libjpeg文件夹,里面再创建includelib

把编辑后的libjpeg.lib放在lib目录下,所有的.h的头文件放在include

处理好的文件如下

链接:https://pan.baidu.com/s/1vEjL6CHgFDuqQioLq3dhJw
提取码:qeb3

项目引用

创建C++控制台项目

打开项目所在目录,在项目下创建modules文件夹,把之前的libjpeg文件夹放进来

项目右键属性

image-20220921111226922

一定要注意,这里要和运行的环境一致。

C/C++常规中的附加包含目录中添加modules\libjpeg\include

image-20210708144103726

链接器=>常规=>附加库目录中添加modules\libjpeg\lib

image-20210708144357439

链接器=>输入=>附加依赖项中添加libjpeg.lib

image-20210708144602929

这样项目中就可以调用了

代码

截屏

ScreenShot.h

1
2
#pragma once
bool ScreenShot(const char* szSavePath);

ScreenShot.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <Windows.h>
#include <stdio.h>
#include <tchar.h>

#include <iostream>
using namespace std;

#pragma warning(disable:4996)

#define TAG_DEV_PLAS 1
#define BITS_PER_PIX 32
#define NO_COLOR_TAB 0
#define UNCMP_RGB 0
#define H_RESOL_0 0
#define V_RESOL_0 0
#define ALL_COLOR 0

#define MUST_ZERO 0
#define TYPE_BMP 0x4D42

#define FILE_HEAD sizeof(BITMAPFILEHEADER)
#define INFO_HEAD sizeof(BITMAPINFOHEADER)
#define HEAD_SIZE sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER)

bool ScreenShot(const char* szSavePath)
{
//显示器屏幕
HDC hCurrScreen = GetDC(NULL);

//创建一个兼容的DC,在内存中表示当前位图的上下文
HDC hCmpDC = CreateCompatibleDC(hCurrScreen);

//宽高
int iScreenWidth = GetDeviceCaps(hCurrScreen, HORZRES);
int iScreenHeight = GetDeviceCaps(hCurrScreen, VERTRES);

//当前屏幕位图
HBITMAP hBmp = CreateCompatibleBitmap(hCurrScreen, iScreenWidth, iScreenHeight);

//用当前位图句柄表示内存中屏幕位图上下文
SelectObject(hCmpDC, hBmp);

//将当前屏幕图像复制到内存中
BOOL ret = BitBlt(hCmpDC, 0, 0, iScreenWidth, iScreenHeight, hCurrScreen, 0, 0, SRCCOPY);

//BMP图像信息头
BITMAPINFOHEADER hBmpInfo;
hBmpInfo.biSize = INFO_HEAD;
hBmpInfo.biWidth = iScreenWidth;
hBmpInfo.biHeight = iScreenHeight;
hBmpInfo.biPlanes = TAG_DEV_PLAS;
hBmpInfo.biClrUsed = NO_COLOR_TAB;
hBmpInfo.biBitCount = BITS_PER_PIX;
hBmpInfo.biSizeImage = UNCMP_RGB;
hBmpInfo.biCompression = BI_RGB;
hBmpInfo.biClrImportant = ALL_COLOR;
hBmpInfo.biXPelsPerMeter = H_RESOL_0;
hBmpInfo.biYPelsPerMeter = V_RESOL_0;

/* * * * * * * * * * * * * * * * * * * *
* Windows按4字节分配内存
* 首先计算每行所需要的bit数,并按4字节对齐
* 对齐后的数据乘4,从DWORD转为BYTE
* 每行实际所占BYTE乘图像列数得到数据源大小
* * * * * * * * * * * * * * * * * * * */
DWORD dwSrcSize = ((iScreenWidth * hBmpInfo.biBitCount + 31) / 32) * 4 * iScreenHeight;

//截图总大小
DWORD dwPicSize = HEAD_SIZE + dwSrcSize;

//BMP图像文件头
BITMAPFILEHEADER hBmpFile;
hBmpFile.bfSize = dwPicSize;
hBmpFile.bfType = TYPE_BMP;
hBmpFile.bfOffBits = HEAD_SIZE;
hBmpFile.bfReserved1 = MUST_ZERO;
hBmpFile.bfReserved2 = MUST_ZERO;

//BMP图像数据源
char* bmpSrc = new char[dwSrcSize];
ZeroMemory(bmpSrc, dwSrcSize);

//检索指定的兼容位图中的所有位元数据
//并复制到指定格式的设备无关位图的缓存中
GetDIBits(hCmpDC, hBmp, 0, (UINT)iScreenHeight, bmpSrc, (BITMAPINFO*)&hBmpInfo, DIB_RGB_COLORS);

//汇总所有数据信息
char* szBmp = new char[dwPicSize];
ZeroMemory(szBmp, dwPicSize);
memcpy(szBmp, (void*)&hBmpFile, FILE_HEAD);
memcpy(szBmp + FILE_HEAD, (void*)&hBmpInfo, INFO_HEAD);
memcpy(szBmp + HEAD_SIZE, bmpSrc, dwSrcSize);

//保存BMP图像
FILE* hFile = fopen(szSavePath, "wb+");
if (nullptr != hFile)
{
size_t count = fwrite(szBmp, 1, dwPicSize, hFile);
fclose(hFile);
}

//释放资源
DeleteObject(hBmp);
DeleteObject(hCmpDC);
ReleaseDC(NULL, hCurrScreen);
delete[] szBmp;
delete[] bmpSrc;
szBmp = nullptr;
bmpSrc = nullptr;
return true;
}

BMP转JPEG

Bmp2Jpeg.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma once

class CBmp2Jpeg
{
public:
CBmp2Jpeg();
~CBmp2Jpeg();

public:
int Bmp2Jpeg(const char* bmp, const char* jpeg);

private:
int SaveJpeg(const char* filename, unsigned char* bits, int width, int height, int depth);
int ReadBmp(const char* bmp, unsigned char** data, int& w, int& h, int& d);
void Bgra2Rgb(const unsigned char* src, int w, int h, int d, unsigned char* dst);
void InitFileHeader(void* pFile, void* fileHeader);
void InitInfoHeader(void* pFile, void* infoHeader);
void SaveBmp(void* fileHeader, void* infoHeader, int bitCount, unsigned char* data, const char* savename);
private:
int m_quality; //它的大小决定jpg的质量好坏

enum {
JPEG_QUALITY = 100,
};
};

Bmp2Jpeg.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
#include <iostream>
#include<Windows.h>
#include <fstream>
#include <stdlib.h>
#include "Bmp2Jpeg.h"
#include <vector>

extern "C"
{
#include "jpeglib.h"
};

#pragma comment(lib,"libjpeg.lib")

using namespace std;

#pragma pack(2)

struct bmp_fileheader //文件头,长度为14Byte固定
{
unsigned short bfType;
unsigned long bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned long bfOffBits;
};

struct bmp_infoheader //文件信息头,长度为40Byte固定
{
unsigned long biSize;
unsigned long biWidth;
unsigned long biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned long biCompression;
unsigned long biSizeImage;
unsigned long biXPelsPerMeter;
unsigned long biYPelsPerMeter;
unsigned long biClrUsed;
unsigned long biClrImportant;
};

struct RGBPallete
{
unsigned char b;
unsigned char g;
unsigned char r;
unsigned char alpha;
};

CBmp2Jpeg::CBmp2Jpeg() :
m_quality(JPEG_QUALITY)
{
}

CBmp2Jpeg::~CBmp2Jpeg()
{
}
/*===================================================================================
function: jpeg压缩
input: 1:生成的文件名,2:bmp的指针,3:位图宽度,4:位图高度,5:颜色深度
return: int
description: bmp的像素格式为(RGB)
===================================================================================*/
int CBmp2Jpeg::SaveJpeg(const char* filename, unsigned char* bits, int width, int height, int depth)
{
FILE* outfile; /* target file */
fopen_s(&outfile, filename, "wb");
if (outfile == NULL) {
return -1;
}

struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;

cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);

jpeg_stdio_dest(&cinfo, outfile);

cinfo.image_width = width; /* image width and height, in pixels */
cinfo.image_height = height;
cinfo.input_components = 3; /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */

jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, m_quality, TRUE /* limit to baseline-JPEG values */);

jpeg_start_compress(&cinfo, TRUE);

JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
int row_stride; /* physical row width in image buffer */
row_stride = width * depth; /* JSAMPLEs per row in image_buffer */

while (cinfo.next_scanline < cinfo.image_height) {
//这里我做过修改,由于jpg文件的图像是倒的,所以改了一下读的顺序
//row_pointer[0] = & bits[cinfo.next_scanline * row_stride];
row_pointer[0] = &bits[(cinfo.image_height - cinfo.next_scanline - 1) * row_stride];
(void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
}

jpeg_finish_compress(&cinfo);
fclose(outfile);

jpeg_destroy_compress(&cinfo);
return 0;
}

void CBmp2Jpeg::InitFileHeader(void* pFile, void* fileHeader)
{
bmp_fileheader* pfileHeader = (bmp_fileheader*)fileHeader;
fstream* filein = (fstream*)pFile;
//初始化文件头
pfileHeader->bfType = 0;
pfileHeader->bfSize = 0;
pfileHeader->bfReserved1 = 0;
pfileHeader->bfReserved2 = 0;
pfileHeader->bfOffBits = 0;
//读位图文件头并输出相应信息
filein->read((char*)fileHeader, sizeof(bmp_fileheader));
}

void CBmp2Jpeg::InitInfoHeader(void* pFile, void* infoHeader)
{
bmp_infoheader* pinfoHeader = (bmp_infoheader*)infoHeader;
fstream* filein = (fstream*)pFile;
//初始化信息头
pinfoHeader->biSize = 0;
pinfoHeader->biWidth = 0;
pinfoHeader->biHeight = 0;
pinfoHeader->biPlanes = 0;
pinfoHeader->biBitCount = 0;
pinfoHeader->biCompression = 0;
pinfoHeader->biSizeImage = 0;
pinfoHeader->biXPelsPerMeter = 0;
pinfoHeader->biYPelsPerMeter = 0;
pinfoHeader->biClrUsed = 0;
pinfoHeader->biClrImportant = 0;

//读位图信息头并输出相应信息
filein->read((char*)infoHeader, sizeof(bmp_infoheader));
}

void CBmp2Jpeg::SaveBmp(void* fileHeader, void* infoHeader, int bitCount, unsigned char* data, const char* savename)
{
bmp_fileheader* pfileHeader = (bmp_fileheader*)fileHeader;
bmp_infoheader* pinfoHeader = (bmp_infoheader*)infoHeader;
//写入文件
std::string str(savename);
str.append(".bmp");
fstream fileout;
fileout.open(str, std::ios::binary | std::ios::out);
fileout.write((char*)fileHeader, sizeof(bmp_fileheader));
fileout.write((char*)infoHeader, sizeof(bmp_infoheader));
fileout.write((char*)data, sizeof(unsigned char) * pfileHeader->bfSize - pfileHeader->bfOffBits);

fileout.close();
}

//读取并将图片另存为一个新文件, 转换成rgb格式
int CBmp2Jpeg::ReadBmp(const char* bmp, unsigned char** data, int& w, int& h, int& d)
{
//打开位图文件
fstream filein;
filein.open(bmp, std::ios::binary | std::ios::in);
if (!filein.is_open())
{
char clog[256] = { 0 };
sprintf_s(clog, sizeof(clog), "bmp转jpeg,找不到 %s\n", bmp);
OutputDebugStringA(clog);
return -1;
}

//定义变量
long width = 0;
long height = 0;
long bitCount = 0;

bmp_fileheader fileHeader;
bmp_infoheader infoHeader;

InitFileHeader(&filein, &fileHeader);

if (fileHeader.bfType != 0x4d42)
{
filein.close();
return -1;
}

InitInfoHeader(&filein, &infoHeader);

width = infoHeader.biWidth;
height = infoHeader.biHeight;
bitCount = infoHeader.biBitCount;

int bitPerLine = ((width * bitCount + 31) >> 5) << 2;
int imgSize = abs(height * bitPerLine);
int imgReal = fileHeader.bfSize - fileHeader.bfOffBits;
if (imgSize != imgReal)
{
char clog[256] = { 0 };
sprintf_s(clog, sizeof(clog), "bmp转jpeg,图像尺寸不对\n");
OutputDebugStringA(clog);
filein.close();
return -1;
}

if (bitCount == 8)
{
std::vector<RGBPallete> palletes;
unsigned char buf[256 * sizeof(RGBPallete)];

filein.read((char*)buf, 256 * sizeof(RGBPallete));

for (int i = 0; i < 256; i++)
{
RGBPallete pallete;
memcpy(&pallete, buf + i * sizeof(RGBPallete), sizeof(RGBPallete));

palletes.push_back(pallete);
}

unsigned char* pTemp = new unsigned char[imgSize];
filein.read((char*)pTemp, imgSize);

*data = new unsigned char[width * abs(height) * 4];
for (int i = 0; i < imgSize; i++)
{
RGBPallete& p = palletes[pTemp[i]];
memcpy((*data) + i * sizeof(RGBPallete), &p, sizeof(RGBPallete));
}

bitCount = 32;
delete pTemp;
}
else if (bitCount == 24 || bitCount == 32)
{
*data = new unsigned char[imgSize];
filein.read((char*)(*data), imgSize);
filein.close();
}
else
{
filein.close();
return -1;
}

w = width;
h = height;
d = bitCount;

return 0;
}

void CBmp2Jpeg::Bgra2Rgb(const unsigned char* src, int w, int h, int d, unsigned char* dst)
{
unsigned char* pTempDst = dst;
for (int i = 0; i < abs(h); i++)
{
const unsigned char* pTempSrc = nullptr;
if (h > 0)
{
pTempSrc = src + w * i * d;
}
else
{
pTempSrc = src + w * abs(i + h + 1) * d;
}

for (int j = 0; j < w; j++)
{
*(pTempDst) = *(pTempSrc + 2);
*(pTempDst + 1) = *(pTempSrc + 1);
*(pTempDst + 2) = *(pTempSrc);
pTempDst += 3;
pTempSrc += d;
}
}
}

int CBmp2Jpeg::Bmp2Jpeg(const char* bmp, const char* jpeg)
{
unsigned char* brga = nullptr; //指向位图buffer的全局指针,window下像素格式: BGRA(4个字节)
int width = 0, height = 0, depth = 0;

if (ReadBmp(bmp, &brga, width, height, depth) < 0)
{
return -1;
}

unsigned char* rgb = new unsigned char[width * abs(height) * depth / 8];
Bgra2Rgb(brga, width, height, depth / 8, rgb);

int ret = SaveJpeg(jpeg, rgb, width, abs(height), 3);

delete[] brga;
delete[] rgb;
brga = nullptr;
rgb = nullptr;
return ret;
}

这里是类,为了方便导出,我们添加以下方法,为了方便后续的导出。

MyBmp2Jpeg.h

1
2
3
4
#pragma once
#include "Bmp2Jpeg.h"

int MyBmp2Jpeg(const char* bmp, const char* jpeg);

MyBmp2Jpeg.cpp

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <stdio.h>
#include "Bmp2Jpeg.h"

int MyBmp2Jpeg(const char* bmp, const char* jpeg) {
CBmp2Jpeg mbmp;
mbmp.Bmp2Jpeg(bmp, jpeg);
return 0;
}

测试

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <stdio.h>
#include "Bmp2Jpeg.h"
#include "ScreenShot.h"
using namespace std;
int main()
{
ScreenShot("D:\\pic\\2.bmp");
CBmp2Jpeg bmp;
bmp.Bmp2Jpeg("D:\\pic\\2.bmp", "D:\\pic\\2.jpeg");
cout << "success." << endl;
cin.get();
return 0;
}

或者

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <stdio.h>
#include "MyBmp2Jpeg.h"
#include "ScreenShot.h"
using namespace std;
int main()
{
ScreenShot("D:\\pic\\2.bmp");
MyBmp2Jpeg("D:\\pic\\2.bmp", "D:\\pic\\2.jpeg");
cout << "success." << endl;
cin.get();
return 0;
}

暴漏DLL方法

特别注意

生成DLL一定要用Release环境!!!

生成DLL一定要用Release环境!!!

生成DLL一定要用Release环境!!!

否则会依赖的DLL也会用Debug的DLL,在普通用户的环境中是没有这些DLL的。

(1) 修改原项目的配置类型为动态库(.dll)

image-20220921125516309

(2) 项目中添加模块定义文件

image-20220921105316920

screenshot.def

1
2
3
4
LIBRARY screenshot
EXPORTS
ScreenShot @1,
MyBmp2Jpeg @2,

右键项目,点击重新生成,这时候就会生成DLL文件。

注意

文件名和LIBRARY screenshot都要和项目名保持一致

导出都要导出方法,不要导出类,导出类其他语言不支持,所以我们添加一个导出方法MyBmp2Jpeg

查看导出的DLL

在VS目录中搜索dumpbin.exe,添加到环境变量中

1
dumpbin /exports screenshot.dll

可以看到导出的方法

image-20220921125806564

测试导出DLL

在原解决方案中添加新项目screenshot_test,并把新项目设置为启动项目。

screenshot_test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <stdio.h>
#include <Windows.h>

using namespace std;

int main()
{
typedef bool (*ScreenShot)(const char* szSavePath);
typedef int (*MyBmp2Jpeg)(const char* bmp, const char* jpeg);
HINSTANCE hDLL;
ScreenShot mScreenShot;
MyBmp2Jpeg mBmp2Jpeg;
hDLL = LoadLibrary(TEXT("screenshot.dll"));
if (hDLL != 0) {
mScreenShot = (ScreenShot)GetProcAddress(hDLL, "ScreenShot");
mBmp2Jpeg = (MyBmp2Jpeg)GetProcAddress(hDLL, "MyBmp2Jpeg");
mScreenShot("D:\\pic\\003.bmp");
mBmp2Jpeg("D:\\pic\\003.bmp", "D:\\pic\\003.jpeg");
cout << "success." << endl;
cin.get();
FreeLibrary(hDLL);//卸载dll文件;
}
return 0;
}

DLL导出类:
优点:导出的类可以被继承,调用层次也清晰,可以保留类的完整特性;
缺点:不能被其它语言调用(包括C语言),封装性并不是太好。

导出函数:
优点:可以被其它语言调用,使用简单,封装性相对较好;
缺点:调用层次不明显,尤其是在导出函数多的情况下,比较混乱,不能出现同名导出函数。

运行库

https://www.microsoft.com/zh-CN/download/details.aspx?id=48145

https://www.microsoft.com/zh-cn/download/details.aspx?id=26999

C#调用DLL方法

项目文件夹下创建DLLCPP文件夹,把screenshot.dll放进来

属性=>生成事件=>生成前事件命令行中添加

1
xcopy /Y /i /e $(ProjectDir)\DLLCPP $(TargetDir)\

页面中调用

1
2
3
4
5
[DllImport("screenshot.dll", EntryPoint = "ScreenShot", CallingConvention = CallingConvention.Cdecl)]
public extern static bool ScreenShot(string szSavePath);

[DllImport("screenshot.dll", EntryPoint = "MyBmp2Jpeg", CallingConvention = CallingConvention.Cdecl)]
public extern static int MyBmp2Jpeg(string bmp, string jpeg);

调用方式

1
2
ScreenShot("D:\\pic\\2.bmp");
MyBmp2Jpeg("D:\\pic\\2.bmp", "D:\\pic\\2.jpeg");