搞跨平臺IM,截圖功能少不了。

Windows

建立GDI的相容點陣圖,把桌面的影象通過BitBlt拷貝到相容位圖裡,通過相容點陣圖的資料指標建立Bitmap物件,由於相容點陣圖的記憶體是非託管的,Bitmap無法釋放該記憶體,拷貝一下,把相容點陣圖的釋放掉,新的Bitmap的記憶體就可以由新Bitmap來自己託管釋放。

        public override Bitmap Screenshot()
{
var srcDC = GetDC(IntPtr.Zero);
var bounds = Bounds; IntPtr memDc = CreateCompatibleDC(srcDC);
BITMAPINFOHEADER info = new BITMAPINFOHEADER();
info.biSize = (uint)Marshal.SizeOf(typeof(BITMAPINFOHEADER));
info.biBitCount = 32;
info.biHeight = -(int)bounds.Height;
info.biWidth = (int)bounds.Width;
info.biPlanes = 1;
var hBitmap = CreateDIBSection(memDc, ref info, 0, out var ppvBits, IntPtr.Zero, 0);
var oldBits = SelectObject(memDc, hBitmap); BitBlt(memDc, 0, 0, (int)bounds.Width,
(int)bounds.Height, srcDC, (int)bounds.X, (int)bounds.Y, TernaryRasterOperations.SRCCOPY);
Bitmap temp = new Bitmap((int)bounds.Width, (int)bounds.Height, (int)bounds.Width * 4, PixelFormat.Bgra, ppvBits); Bitmap bitmap = (Bitmap)temp.Clone();
temp.Dispose(); SelectObject(memDc, oldBits);
DeleteObject(hBitmap);
DeleteDC(memDc); ReleaseDC(IntPtr.Zero, srcDC);
return bitmap;
}

Mac

直接使用Mac裡的 CGWindowListCreateImage 來截圖,由於資料格式不同,需要讀取畫素,一個個設定給Bitmap

Mac裡在不同許可權選項下擷取的圖片內容不同的,沒有錄製許可權的情況下只能擷取當前程式的介面和空的桌面。

        public unsafe override Bitmap Screenshot()
{
using (var img = new CGImage(CGImage.CGWindowListCreateImage(screen.Frame, CGWindowListOption.All, 0, CGWindowImageOption.Default), owns: true))
{
//CGImage imageRef = CGImage.CGWindowListCreateImage(mainRect, kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageNominalResolution | kCGWindowImageShouldBeOpaque);
var height = img.Height;
var width = img.Width;
var bpr = img.BytesPerRow;
var bpp = img.BitsPerPixel;
var bpc = img.BitsPerComponent;
var bytes_per_pixel = bpp / bpc; using (var data = img.DataProvider.CopyData())
{
var bitmap = new Bitmap(img.Width, img.Height);
var bytes = (byte*)data.Bytes;
using (var b = bitmap.Lock())
{
for (var row = 0; row < height; row++)
{
for (var col = 0; col < width; col++)
{
var pixel = &bytes[row * bpr + col * bytes_per_pixel]; b.SetPixel(col, row, pixel[3], pixel[2], pixel[1], pixel[0]);
}
}
}
return bitmap;
}
}
}

Linux

使用XGetImage來截圖,同樣需要轉換一下點陣圖格式

        public override Bitmap Screenshot()
{
var bounds = Bounds;
var image = XGetImage(LinuxPlatform.Platform.Display, LinuxPlatform.Platform.Info.RootWindow, (int)bounds.X, (int)bounds.Y, (int)bounds.Width,
(int)bounds.Height, ~0, 2 /* ZPixmap*/);
if (image == IntPtr.Zero)
{
string s = String.Format("XGetImage returned NULL when asked to for a {0}x{1} region block",
bounds.Width, bounds.Height);
throw new InvalidOperationException(s);
} Bitmap bmp = new Bitmap((int)bounds.Width, (int)bounds.Height);
var visual = LinuxPlatform.Platform.Info.TransparentVisualInfo;
int red, blue, green;
int red_mask = (int)visual.red_mask;
int blue_mask = (int)visual.blue_mask;
int green_mask = (int)visual.green_mask;
using (var b = bmp.Lock())
{
for (int y = 0; y < bounds.Height; y++)
{
for (int x = 0; x < bounds.Width; x++)
{
var pixel = XGetPixel(image, x, y); switch (visual.depth)
{
case 16: /* 16bbp pixel transformation */
red = (int)((pixel & red_mask) >> 8) & 0xff;
green = (int)(((pixel & green_mask) >> 3)) & 0xff;
blue = (int)((pixel & blue_mask) << 3) & 0xff;
break;
case 24:
case 32:
red = (int)((pixel & red_mask) >> 16) & 0xff;
green = (int)(((pixel & green_mask) >> 8)) & 0xff;
blue = (int)((pixel & blue_mask)) & 0xff;
break;
default:
string text = string.Format("{0}bbp depth not supported.", visual.depth);
throw new NotImplementedException(text);
} b.SetPixel(x, y, 255, (byte)red, (byte)green, (byte)blue);
}
}
} XDestroyImage(image);
return bmp;
}

上面的就是CPF的內部實現,一般你不需要管這些細節,直接呼叫CPF的函式就行了。

最終封裝到CPF就是一行程式碼就行

window.Screen.Screenshot();

要實現QQ截圖的效果的話,先截圖,再搞個全屏的窗體,把截圖內容放到裡面,再對螢幕截圖進行裁剪,繪圖處理。