捕获桌面,将其设为 256 色并通过互联网发送
Capture desktop , Make it 256 color and send it over internet
我正在做一个项目,在这个项目中我需要捕获桌面屏幕并通过互联网将其发送给其他客户端。要压缩图像,我想将其转换为 256 色图像。我有一个通用的 256 色调色板,我正在使用欧几里得距离来查找最近的颜色。问题是我需要以每秒 10-15 帧的速度发送这些图像,而制作 256 色图像需要 7 秒。我想知道其他程序(如 teamviewer 或真正的 VNC)是如何做到这一点的。
for (int y=0;y<900;y++) //loop through the height
for (int x=0;x<1600;x++) //loop through the width
for (int p=0;p<256;p++) //loop through palette colors
{
calculate Euclidean distance of each pixel for each color in pallette and
find the nearest color
** these nested loops take 7 seconds to complete
}
谢谢
编辑 2:我完全删除了旧的 post,因为它不相关! 256 色,我以为你的意思是 256 位 - 而你说的是 256 字节!我想通过将原始坐标 (900 x 900
) 放入我的计算器,然后乘以 256 得到颜色。结果是 20,7360,000
位,大致是 2.5 MB
。压缩后,这可能达到约 1 MB
- 而位颜色等效(除以 8)将是 300 KB
基数,压缩后会小得多。解决方案很简单——拍摄这样的图像确实需要这么长时间。根据计算机性能,您所说的大多数应用程序(例如 teamviewer)具有较低的 FPS 和低得多的图像质量。因此,我很抱歉 - 但解决方案是在您要求的时间内使用像您这样的计算机可能无法完成。
编辑 3:Hans 在您的问题下的评论中做了数学计算 - 我们正在谈论 22 GB
。这不是普通计算机的正常工作。这不是不可能,但从 2015 年开始,家用电脑在一秒钟内处理这么多数据并不常见。
好的。经过几天与许多捕获方法和颜色量化器的斗争之后,我终于找到了解决方案。现在我能够以 10~14 FPS 的速度发送整个桌面图像,并以 20~30 FPS 的速度更改桌面区域。
在我使用的代码中 rcravens's classes to capture screen and changes of screen. Then I cropped the image to 10 samll pieces . After that I took small pieces and made them 256-color using an Octree Color Quantizier explained here 感谢@Dai 向我指出了那个方向。减色后,我将每个片段转换为字节数组并使用 LZ4.Net 库压缩它们。
代码如下:
int all_count = 0;
Bitmap _desktop = null;
Bitmap _merged_bitmap = new Bitmap(1600, 900);
int _height_part_ = 0;
int _total_rows = 10;
Bitmap[] crops = null;
Bitmap[] _new_crops = null;
Stopwatch sw = new Stopwatch();
int _desktop_height = 0;
int _desktop_width = 0;
ImageManipulation.OctreeQuantizer _q ;
RLC.RemoteDesktop.ScreenCapture cap = new RLC.RemoteDesktop.ScreenCapture();
private void CaptureAndSend()
{
sw.Restart();
//cap = new RLC.RemoteDesktop.ScreenCapture();
int _left = -1, _top = -1; //Changed regions
_desktop = cap.Screen(out _left, out _top); //Capture desktop or changed region of it
if (_desktop == null) return; //if nothing has changed since last capture skip everything
_desktop_height = _desktop.Height;
_desktop_width = _desktop.Width;
// If very small part has changed since last capture skip everything
if (_desktop_height < 10 || _desktop_width < 10) return;
TotalRows(_total_rows); // Calculate the total number of rows
crops = new Bitmap[_total_rows]; // Cropped pieces of image
_new_crops = new Bitmap[_total_rows];
for (int i = 0; i < _total_rows - 1; i++) //Take whole image and split it into smaller images
crops[i] = CropRow(i);
crops[_total_rows - 1] = CropLastRow(_total_rows - 1);
Parallel.For(0, _total_rows, i =>
{
ImageManipulation.OctreeQuantizer _q = new ImageManipulation.OctreeQuantizer(255, 4); // Initialize Octree
_new_crops[i] = _q.Quantize(crops[i]);
using (MemoryStream ms=new MemoryStream())
{
_new_crops[i].Save(ms, ImageFormat.Png);
//Install-Package LZ4.net
//Compress each part and send them over network
byte[] data = Lz4Net.Lz4.CompressBytes(ms.ToArray(), Lz4Net.Lz4Mode.HighCompression);
all_count += data.Length; //Just to check the final size of image
}
});
Console.WriteLine(String.Format("{0:0.0} FPS , {1} seconds , size {2} kb", 1.0 / sw.Elapsed.TotalSeconds, sw.Elapsed.TotalSeconds.ToString(), all_count / 1024));
all_count = 0;
}
private void TotalRows(int parts)
{
_height_part_ = _desktop_height / parts;
}
private Bitmap CropRow(int row)
{
return Crop(_desktop, new Rectangle(0, row * _height_part_, _desktop_width, _height_part_));
}
private Bitmap CropLastRow(int row)
{
return Crop(_desktop, new Rectangle(0, row * _height_part_, _desktop_width, _desktop_height - (row * _height_part_)));
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private unsafe static extern int memcpy(byte* dest, byte* src, long count);
private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle)
{
if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height))
return srcImg;
var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4
var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp;
var srcStride = srcImgBitmapData.Stride;
var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat);
var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer();
var dstStride = dstImgBitmapData.Stride;
for (int y = 0; y < rectangle.Height; y++)
{
memcpy(dstPtr, srcPtr, dstStride);
srcPtr += srcStride;
dstPtr += dstStride;
}
srcImg.UnlockBits(srcImgBitmapData);
dstImg.UnlockBits(dstImgBitmapData);
return dstImg;
}
我知道我的代码内存效率不高。如果有人可以帮助我优化此代码,我将不胜感激。
再次感谢我的朋友 A. Abramov 、 Dai 、 HansPassant 、 TaW 和其他人。
我正在做一个项目,在这个项目中我需要捕获桌面屏幕并通过互联网将其发送给其他客户端。要压缩图像,我想将其转换为 256 色图像。我有一个通用的 256 色调色板,我正在使用欧几里得距离来查找最近的颜色。问题是我需要以每秒 10-15 帧的速度发送这些图像,而制作 256 色图像需要 7 秒。我想知道其他程序(如 teamviewer 或真正的 VNC)是如何做到这一点的。
for (int y=0;y<900;y++) //loop through the height
for (int x=0;x<1600;x++) //loop through the width
for (int p=0;p<256;p++) //loop through palette colors
{
calculate Euclidean distance of each pixel for each color in pallette and
find the nearest color
** these nested loops take 7 seconds to complete
}
谢谢
编辑 2:我完全删除了旧的 post,因为它不相关! 256 色,我以为你的意思是 256 位 - 而你说的是 256 字节!我想通过将原始坐标 (900 x 900
) 放入我的计算器,然后乘以 256 得到颜色。结果是 20,7360,000
位,大致是 2.5 MB
。压缩后,这可能达到约 1 MB
- 而位颜色等效(除以 8)将是 300 KB
基数,压缩后会小得多。解决方案很简单——拍摄这样的图像确实需要这么长时间。根据计算机性能,您所说的大多数应用程序(例如 teamviewer)具有较低的 FPS 和低得多的图像质量。因此,我很抱歉 - 但解决方案是在您要求的时间内使用像您这样的计算机可能无法完成。
编辑 3:Hans 在您的问题下的评论中做了数学计算 - 我们正在谈论 22 GB
。这不是普通计算机的正常工作。这不是不可能,但从 2015 年开始,家用电脑在一秒钟内处理这么多数据并不常见。
好的。经过几天与许多捕获方法和颜色量化器的斗争之后,我终于找到了解决方案。现在我能够以 10~14 FPS 的速度发送整个桌面图像,并以 20~30 FPS 的速度更改桌面区域。
在我使用的代码中 rcravens's classes to capture screen and changes of screen. Then I cropped the image to 10 samll pieces . After that I took small pieces and made them 256-color using an Octree Color Quantizier explained here 感谢@Dai 向我指出了那个方向。减色后,我将每个片段转换为字节数组并使用 LZ4.Net 库压缩它们。
代码如下:
int all_count = 0;
Bitmap _desktop = null;
Bitmap _merged_bitmap = new Bitmap(1600, 900);
int _height_part_ = 0;
int _total_rows = 10;
Bitmap[] crops = null;
Bitmap[] _new_crops = null;
Stopwatch sw = new Stopwatch();
int _desktop_height = 0;
int _desktop_width = 0;
ImageManipulation.OctreeQuantizer _q ;
RLC.RemoteDesktop.ScreenCapture cap = new RLC.RemoteDesktop.ScreenCapture();
private void CaptureAndSend()
{
sw.Restart();
//cap = new RLC.RemoteDesktop.ScreenCapture();
int _left = -1, _top = -1; //Changed regions
_desktop = cap.Screen(out _left, out _top); //Capture desktop or changed region of it
if (_desktop == null) return; //if nothing has changed since last capture skip everything
_desktop_height = _desktop.Height;
_desktop_width = _desktop.Width;
// If very small part has changed since last capture skip everything
if (_desktop_height < 10 || _desktop_width < 10) return;
TotalRows(_total_rows); // Calculate the total number of rows
crops = new Bitmap[_total_rows]; // Cropped pieces of image
_new_crops = new Bitmap[_total_rows];
for (int i = 0; i < _total_rows - 1; i++) //Take whole image and split it into smaller images
crops[i] = CropRow(i);
crops[_total_rows - 1] = CropLastRow(_total_rows - 1);
Parallel.For(0, _total_rows, i =>
{
ImageManipulation.OctreeQuantizer _q = new ImageManipulation.OctreeQuantizer(255, 4); // Initialize Octree
_new_crops[i] = _q.Quantize(crops[i]);
using (MemoryStream ms=new MemoryStream())
{
_new_crops[i].Save(ms, ImageFormat.Png);
//Install-Package LZ4.net
//Compress each part and send them over network
byte[] data = Lz4Net.Lz4.CompressBytes(ms.ToArray(), Lz4Net.Lz4Mode.HighCompression);
all_count += data.Length; //Just to check the final size of image
}
});
Console.WriteLine(String.Format("{0:0.0} FPS , {1} seconds , size {2} kb", 1.0 / sw.Elapsed.TotalSeconds, sw.Elapsed.TotalSeconds.ToString(), all_count / 1024));
all_count = 0;
}
private void TotalRows(int parts)
{
_height_part_ = _desktop_height / parts;
}
private Bitmap CropRow(int row)
{
return Crop(_desktop, new Rectangle(0, row * _height_part_, _desktop_width, _height_part_));
}
private Bitmap CropLastRow(int row)
{
return Crop(_desktop, new Rectangle(0, row * _height_part_, _desktop_width, _desktop_height - (row * _height_part_)));
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private unsafe static extern int memcpy(byte* dest, byte* src, long count);
private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle)
{
if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height))
return srcImg;
var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4
var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp;
var srcStride = srcImgBitmapData.Stride;
var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat);
var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer();
var dstStride = dstImgBitmapData.Stride;
for (int y = 0; y < rectangle.Height; y++)
{
memcpy(dstPtr, srcPtr, dstStride);
srcPtr += srcStride;
dstPtr += dstStride;
}
srcImg.UnlockBits(srcImgBitmapData);
dstImg.UnlockBits(dstImgBitmapData);
return dstImg;
}
我知道我的代码内存效率不高。如果有人可以帮助我优化此代码,我将不胜感激。 再次感谢我的朋友 A. Abramov 、 Dai 、 HansPassant 、 TaW 和其他人。