/// <inheritdoc/>s public bool FramebufferManualEndUpdate() { var fb = this.Framebuffer; if (this.clientWidth != fb.Width || this.clientHeight != fb.Height) { if (this.clientEncoding.Contains(VncEncoding.PseudoDesktopSize)) { var region = new VncRectangle(0, 0, fb.Width, fb.Height); this.AddRegion(region, VncEncoding.PseudoDesktopSize, new byte[0]); this.clientWidth = this.Framebuffer.Width; this.clientHeight = this.Framebuffer.Height; } } if (this.fbuRectangles.Count == 0) { return(false); } this.FramebufferUpdateRequest = null; this.SendRectangles(this.fbuRectangles); this.fbuRectangles.Clear(); return(true); }
private void HandleFramebufferUpdateRequest() { var incremental = this.c.ReceiveByte() != 0; var region = this.c.ReceiveRectangle(); if (!incremental && this.ClientEncodings.Contains(VncEncoding.ExtendedDesktopSize) && this.fbSource.SupportsResizing) { List <Rectangle> rectangles = new List <Rectangle>(); rectangles.Add( this.GetExtendedDesktopSizeRectangle( ExtendedDesktopSizeReason.External, ExtendedDesktopSizeStatus.Success)); this.SendRectangles(rectangles); } lock (this.FramebufferUpdateRequestLock) { this.logger?.LogDebug($"Received a FramebufferUpdateRequest command for {region}"); region = VncRectangle.Intersect(region, new VncRectangle(0, 0, this.Framebuffer.Width, this.Framebuffer.Height)); if (region.IsEmpty) { return; } this.FramebufferUpdateRequest = new FramebufferUpdateRequest(incremental, region); this.FramebufferChanged(); } }
/// <summary> /// Copies a region of the framebuffer into a bitmap. /// </summary> /// <param name="source">The framebuffer to read.</param> /// <param name="sourceRectangle">The framebuffer region to copy.</param> /// <param name="target">The bitmap to copy into.</param> /// <param name="targetX">The leftmost X coordinate of the bitmap to draw to.</param> /// <param name="targetY">The topmost Y coordinate of the bitmap to draw to.</param> public unsafe static void CopyFromFramebuffer(VncFramebuffer source, VncRectangle sourceRectangle, Bitmap target, int targetX, int targetY) { Throw.If.Null(source, "source").Null(target, "target"); if (sourceRectangle.IsEmpty) { return; } var winformsRect = new Rectangle(targetX, targetY, sourceRectangle.Width, sourceRectangle.Height); var data = target.LockBits(winformsRect, ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb); try { fixed(byte *framebufferData = source.GetBuffer()) { VncPixelFormat.Copy((IntPtr)framebufferData, source.Stride, source.PixelFormat, sourceRectangle, data.Scan0, data.Stride, new VncPixelFormat()); } } finally { target.UnlockBits(data); } }
/// <summary> /// Queues an update corresponding to one region of the framebuffer being copied to another. /// </summary> /// <param name="target"> /// The updated <see cref="VncRectangle"/>. /// </param> /// <param name="sourceX"> /// The X coordinate of the source. /// </param> /// <param name="sourceY"> /// The Y coordinate of the source. /// </param> /// <remarks> /// Do not call this method without holding <see cref="VncServerSession.FramebufferUpdateRequestLock"/>. /// </remarks> public void FramebufferManualCopyRegion(VncRectangle target, int sourceX, int sourceY) { if (!this.clientEncoding.Contains(VncEncoding.CopyRect)) { var source = new VncRectangle(sourceX, sourceY, target.Width, target.Height); var region = VncRectangle.Union(source, target); if (region.Area > source.Area + target.Area) { this.FramebufferManualInvalidate(new[] { source, target }); } else { this.FramebufferManualInvalidate(region); } return; } var contents = new byte[4]; VncUtility.EncodeUInt16BE(contents, 0, (ushort)sourceX); VncUtility.EncodeUInt16BE(contents, 2, (ushort)sourceY); this.AddRegion(target, VncEncoding.CopyRect, contents); }
/// <summary> /// 将需要更新位图的区域复制到framebuffer中 /// </summary> /// <param name="source">图片源.</param> /// <param name="sourceRectangle">要复制的位图区域.</param> /// <param name="target">目标帧缓冲区</param> /// <param name="targetX">帧缓冲区的左顶点X坐标</param> /// <param name="targetY">帧缓冲区的左顶点Y坐标</param> public unsafe static void CopyToFramebuffer(Bitmap source, VncRectangle sourceRectangle, VncFramebuffer target, int targetX, int targetY) { Throw.If.Null(source, "source").Null(target, "target"); if (sourceRectangle.IsEmpty) { return; } var winformsRect = new Rectangle(sourceRectangle.X, sourceRectangle.Y, sourceRectangle.Width, sourceRectangle.Height); var data = source.LockBits(winformsRect, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb); try { fixed(byte *framebufferData = target.GetBuffer()) { VncPixelFormat.Copy(data.Scan0, data.Stride, new VncPixelFormat(32, 24, 8, 16, 8, 8, 8, 0), sourceRectangle, (IntPtr)framebufferData, target.Stride, target.PixelFormat, targetX, targetY); } } finally { source.UnlockBits(data); } }
/// <inheritdoc/> public void FramebufferManualInvalidate(VncRectangle region) { var fb = this.Framebuffer; var cpf = this.clientPixelFormat; region = VncRectangle.Intersect(region, new VncRectangle(0, 0, this.clientWidth, this.clientHeight)); if (region.IsEmpty) { return; } int x = region.X, y = region.Y, w = region.Width, h = region.Height, bpp = cpf.BytesPerPixel; var contents = new byte[w * h * bpp]; VncPixelFormat.Copy( fb.GetBuffer(), fb.Width, fb.Stride, fb.PixelFormat, region, contents, w, w * bpp, cpf); this.AddRegion(region, VncEncoding.Raw, contents); }
public bool RespondToUpdateRequest(VncServerSession session) { var fb = Framebuffer; var fbr = session.FramebufferUpdateRequest; if (fb == null || fbr == null) { return(false); } var incremental = fbr.Incremental; var region = fbr.Region; session.FramebufferManualBeginUpdate(); var buffer = fb.GetBuffer(); int bpp = fb.PixelFormat.BytesPerPixel; lock (fb.SyncRoot) { int ymax = Math.Min(region.Y + region.Height, fb.Height); int xmax = Math.Min(region.X + region.Width, fb.Width); for (int y = region.Y; y < ymax; y += TileSize) { for (int x = region.X; x < xmax; x += TileSize) { int w = Math.Min(TileSize, xmax - x); int h = Math.Min(TileSize, ymax - y); var subregion = new VncRectangle(x, y, w, h); VncPixelFormat.Copy(buffer, fb.Stride, fb.PixelFormat, subregion, _pixelBuffer, w * bpp, Framebuffer.PixelFormat); int ix = x / TileSize, iy = y / TileSize; var tileHash = _hash.ComputeHash(_pixelBuffer, 0, w * h * bpp); if (_hashes[iy, ix] == null || !_hashes[iy, ix].SequenceEqual(tileHash)) { _hashes[iy, ix] = tileHash; if (incremental) { session.FramebufferManualInvalidate(subregion); } } } } } if (!incremental) { session.FramebufferManualInvalidate(region); } return(session.FramebufferManualEndUpdate()); }
/// <inheritdoc/> public override void Send(Stream stream, VncPixelFormat pixelFormat, VncRectangle region, byte[] contents) { this.buffer.SetLength(0); this.deflater.Write(contents, 0, contents.Length); this.deflater.Flush(); this.buffer.Position = 0; byte[] length = new byte[4]; VncUtility.EncodeUInt32BE(length, 0, (uint)this.buffer.Length); stream.Write(length, 0, 4); this.buffer.CopyTo(stream); }
/// <inheritdoc/> public void FramebufferManualInvalidate(VncRectangle region) { var fb = this.Framebuffer; var cpf = this.clientPixelFormat; region = VncRectangle.Intersect(region, new VncRectangle(0, 0, this.clientWidth, this.clientHeight)); if (region.IsEmpty) { return; } int x = region.X, y = region.Y, w = region.Width, h = region.Height, bpp = cpf.BytesPerPixel; var contents = new byte[w * h * bpp]; VncPixelFormat.Copy( fb.GetBuffer(), fb.Width, fb.Stride, fb.PixelFormat, region, contents, w, w * bpp, cpf); #if DEFLATESTREAM_FLUSH_WORKS if (_clientEncoding.Contains(VncEncoding.Zlib)) { _zlibMemoryStream.Position = 0; _zlibMemoryStream.SetLength(0); _zlibMemoryStream.Write(new byte[4], 0, 4); if (_zlibDeflater == null) { _zlibMemoryStream.Write(new[] { (byte)120, (byte)218 }, 0, 2); _zlibDeflater = new DeflateStream(_zlibMemoryStream, CompressionMode.Compress, false); } _zlibDeflater.Write(contents, 0, contents.Length); _zlibDeflater.Flush(); contents = _zlibMemoryStream.ToArray(); VncUtility.EncodeUInt32BE(contents, 0, (uint)(contents.Length - 4)); AddRegion(region, VncEncoding.Zlib, contents); } else #endif { this.AddRegion(region, VncEncoding.Raw, contents); } }
void AddRegion(VncRectangle region, VncEncoding encoding, byte[] contents) { _fbuRectangles.Add(new Rectangle() { Region = region, Encoding = encoding, Contents = contents }); // Avoid the overflow of updated rectangle count. // NOTE: EndUpdate may implicitly add one for desktop resizing. if (_fbuRectangles.Count >= ushort.MaxValue - 1) { FramebufferManualEndUpdate(); FramebufferManualBeginUpdate(); } }
/// <summary> /// Copies a region of a bitmap into the framebuffer. /// </summary> /// <param name="source">The bitmap to read.</param> /// <param name="sourceRectangle">The bitmap region to copy.</param> /// <param name="target">The framebuffer to copy into.</param> /// <param name="targetX">The leftmost X coordinate of the framebuffer to draw to.</param> /// <param name="targetY">The topmost Y coordinate of the framebuffer to draw to.</param> public static unsafe void CopyToFramebuffer( Bitmap source, VncRectangle sourceRectangle, VncFramebuffer target, int targetX, int targetY) { if (source == null) { throw new ArgumentNullException(nameof(source)); } if (target == null) { throw new ArgumentNullException(nameof(target)); } if (sourceRectangle.IsEmpty) { return; } var winformsRect = new Rectangle(sourceRectangle.X, sourceRectangle.Y, sourceRectangle.Width, sourceRectangle.Height); var data = source.LockBits(winformsRect, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb); try { fixed(byte *framebufferData = target.GetBuffer()) { VncPixelFormat.Copy( data.Scan0, data.Stride, new VncPixelFormat(), sourceRectangle, (IntPtr)framebufferData, target.Stride, target.PixelFormat, targetX, targetY); } } finally { source.UnlockBits(data); } }
/// <inheritdoc/>s public bool FramebufferManualEndUpdate() { var fb = this.Framebuffer; if (this.clientWidth != fb.Width || this.clientHeight != fb.Height) { if (this.clientEncoding.Contains(VncEncoding.PseudoDesktopSize)) { var region = new VncRectangle(0, 0, fb.Width, fb.Height); this.AddRegion(region, VncEncoding.PseudoDesktopSize, new byte[0]); this.clientWidth = this.Framebuffer.Width; this.clientHeight = this.Framebuffer.Height; } } if (this.fbuRectangles.Count == 0) { return(false); } this.FramebufferUpdateRequest = null; lock (this.c.SyncRoot) { this.c.Send(new byte[2] { 0, 0 }); this.c.SendUInt16BE((ushort)this.fbuRectangles.Count); foreach (var rectangle in this.fbuRectangles) { Debug.Assert(rectangle.Encoding == VncEncoding.Raw, "Rectangles should be in raw format"); this.c.SendRectangle(rectangle.Region); this.c.SendUInt32BE((uint)this.Encoder.Encoding); this.Encoder.Send(this.c.Stream, this.clientPixelFormat, rectangle.Region, rectangle.Contents); } this.fbuRectangles.Clear(); return(true); } }
private void HandleFramebufferUpdateRequest() { var incremental = this.c.ReceiveByte() != 0; var region = this.c.ReceiveRectangle(); lock (this.FramebufferUpdateRequestLock) { this.logger?.Log(LogLevel.Info, () => $"Received a FramebufferUpdateRequest command for {region}"); region = VncRectangle.Intersect(region, new VncRectangle(0, 0, this.Framebuffer.Width, this.Framebuffer.Height)); if (region.IsEmpty) { return; } this.FramebufferUpdateRequest = new FramebufferUpdateRequest(incremental, region); this.FramebufferChanged(); } }
/// <summary> /// Copies a region of the framebuffer into a bitmap. /// </summary> /// <param name="source">The framebuffer to read.</param> /// <param name="sourceRectangle">The framebuffer region to copy.</param> /// <param name="target">The bitmap to copy into.</param> /// <param name="targetX">The leftmost X coordinate of the bitmap to draw to.</param> /// <param name="targetY">The topmost Y coordinate of the bitmap to draw to.</param> public unsafe static void CopyFromFramebuffer(VncFramebuffer source, VncRectangle sourceRectangle, Bitmap target, int targetX, int targetY) { Throw.If.Null(source, "source").Null(target, "target"); if (sourceRectangle.IsEmpty) { return; } var winformsRect = new Rectangle(targetX, targetY, sourceRectangle.Width, sourceRectangle.Height); var data = target.LockBits(winformsRect, ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb); try { fixed (byte* framebufferData = source.GetBuffer()) { VncPixelFormat.Copy((IntPtr)framebufferData, source.Stride, source.PixelFormat, sourceRectangle, data.Scan0, data.Stride, new VncPixelFormat()); } } finally { target.UnlockBits(data); } }
/// <inheritdoc/> public override int Send(Stream stream, VncPixelFormat pixelFormat, VncRectangle region, byte[] contents) { var jpegQualityLevel = GetQualityLevel(this.VncServerSession); // The JPEG compression currently assumes a RGB32 pixel format, fall back to basic compression // when this pixel format is not available. // Additionally, a minimal JPEG image is at least ~128 bytes in size, so only compress to JPEG // when the uncompressed file significantly larger. if (this.Compression != TightCompression.Jpeg || !VncPixelFormat.RGB32.Equals(pixelFormat) || region.IsEmpty || contents.Length < 256 || jpegQualityLevel == 0) { return(this.SendWithBasicCompression(stream, pixelFormat, region, contents)); } else { return(this.SendWithJpegCompression(stream, pixelFormat, region, contents, jpegQualityLevel)); } }
/// <inheritdoc/> public void FramebufferManualInvalidate(VncRectangle region) { var fb = this.Framebuffer; var cpf = this.clientPixelFormat; region = VncRectangle.Intersect(region, new VncRectangle(0, 0, this.clientWidth, this.clientHeight)); if (region.IsEmpty) { return; } int x = region.X, y = region.Y, w = region.Width, h = region.Height, bpp = cpf.BytesPerPixel; var contents = new byte[w * h * bpp]; VncPixelFormat.Copy( fb.GetBuffer(), fb.Width, fb.Stride, fb.PixelFormat, region, contents, w, w * bpp, cpf); if (clientEncoding.Contains(VncEncoding.Zlib)) { byte[] zlibContents = Compress(contents); var lenArray = new byte[4]; VncUtility.EncodeUInt32BE(lenArray, 0, (uint)(zlibContents.Length)); var finArray = Combine(lenArray, zlibContents); AddRegion(region, VncEncoding.Zlib, finArray); } else { this.AddRegion(region, VncEncoding.Raw, contents); } }
/// <summary> /// Completes a manual framebuffer update. /// /// Do not call this method without holding <see cref="VncServerSession.FramebufferUpdateRequestLock"/>. /// </summary> public bool FramebufferManualEndUpdate() { var fb = Framebuffer; if (_clientWidth != fb.Width || _clientHeight != fb.Height) { if (_clientEncoding.Contains(VncEncoding.PseudoDesktopSize)) { var region = new VncRectangle(0, 0, fb.Width, fb.Height); AddRegion(region, VncEncoding.PseudoDesktopSize, new byte[0]); _clientWidth = Framebuffer.Width; _clientHeight = Framebuffer.Height; } } if (_fbuRectangles.Count == 0) { return(false); } FramebufferUpdateRequest = null; lock (_c.SyncRoot) { _c.Send(new byte[2] { 0, 0 }); _c.SendUInt16BE((ushort)_fbuRectangles.Count); foreach (var rectangle in _fbuRectangles) { _c.SendRectangle(rectangle.Region); _c.SendUInt32BE((uint)rectangle.Encoding); _c.Send(rectangle.Contents); } _fbuRectangles.Clear(); return(true); } }
/// <summary> /// Copies a region of the framebuffer into a bitmap. /// </summary> /// <param name="source">The framebuffer to read.</param> /// <param name="sourceRectangle">The framebuffer region to copy.</param> /// <param name="target">The bitmap to copy into.</param> /// <param name="targetX">The leftmost X coordinate of the bitmap to draw to.</param> /// <param name="targetY">The topmost Y coordinate of the bitmap to draw to.</param> public static unsafe void CopyFromFramebuffer( VncFramebuffer source, VncRectangle sourceRectangle, Bitmap target, int targetX, int targetY) { if (target == null) { throw new ArgumentNullException(nameof(target)); } var winformsRect = new Rectangle(targetX, targetY, sourceRectangle.Width, sourceRectangle.Height); var data = target.LockBits(winformsRect, ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb); try { VncPixelFormat.CopyFromFramebuffer(source, sourceRectangle, data.Scan0, data.Stride, targetX, targetY); } finally { target.UnlockBits(data); } }
private void AddRegion(VncRectangle region, VncEncoding encoding, byte[] contents) { this.fbuRectangles.Add(new Rectangle() { Region = region, Encoding = encoding, Contents = contents }); // Avoid the overflow of updated rectangle count. // NOTE: EndUpdate may implicitly add one for desktop resizing. if (this.fbuRectangles.Count >= ushort.MaxValue - 1) { this.FramebufferManualEndUpdate(); this.FramebufferManualBeginUpdate(); } }
/// <summary> /// Completes a manual framebuffer update. /// </summary> /// <returns> /// <see langword="true"/> if the operation completed successfully; otherwise, /// <see langword="false"/>. /// </returns> /// <remarks> /// Do not call this method without holding <see cref="VncServerSession.FramebufferUpdateRequestLock"/>. /// </remarks> public bool FramebufferManualEndUpdate() { var fb = this.Framebuffer; if (this.clientWidth != fb.Width || this.clientHeight != fb.Height) { if (this.clientEncoding.Contains(VncEncoding.PseudoDesktopSize)) { var region = new VncRectangle(0, 0, fb.Width, fb.Height); this.AddRegion(region, VncEncoding.PseudoDesktopSize, new byte[0]); this.clientWidth = this.Framebuffer.Width; this.clientHeight = this.Framebuffer.Height; } } if (this.fbuRectangles.Count == 0) { return false; } this.FramebufferUpdateRequest = null; lock (this.c.SyncRoot) { this.c.Send(new byte[2] { 0, 0 }); this.c.SendUInt16BE((ushort)this.fbuRectangles.Count); foreach (var rectangle in this.fbuRectangles) { this.c.SendRectangle(rectangle.Region); this.c.SendUInt32BE((uint)rectangle.Encoding); this.c.Send(rectangle.Contents); } this.fbuRectangles.Clear(); return true; } }
/// <summary> /// Queues an update for each of the specified regions. /// /// Do not call this method without holding <see cref="VncServerSession.FramebufferUpdateRequestLock"/>. /// </summary> /// <param name="regions">The regions to invalidate.</param> public void FramebufferManualInvalidate(VncRectangle[] regions) { Throw.If.Null(regions, "regions"); foreach (var region in regions) { this.FramebufferManualInvalidate(region); } }
/// <summary> /// Queues an update for the specified region. /// /// Do not call this method without holding <see cref="VncServerSession.FramebufferUpdateRequestLock"/>. /// </summary> /// <param name="region">The region to invalidate.</param> public void FramebufferManualInvalidate(VncRectangle region) { var fb = this.Framebuffer; var cpf = this.clientPixelFormat; region = VncRectangle.Intersect(region, new VncRectangle(0, 0, this.clientWidth, this.clientHeight)); if (region.IsEmpty) { return; } int x = region.X, y = region.Y, w = region.Width, h = region.Height, bpp = cpf.BytesPerPixel; var contents = new byte[w * h * bpp]; VncPixelFormat.Copy( fb.GetBuffer(), fb.Width, fb.Stride, fb.PixelFormat, region, contents, w, w * bpp, cpf); #if DEFLATESTREAM_FLUSH_WORKS if (_clientEncoding.Contains(VncEncoding.Zlib)) { _zlibMemoryStream.Position = 0; _zlibMemoryStream.SetLength(0); _zlibMemoryStream.Write(new byte[4], 0, 4); if (_zlibDeflater == null) { _zlibMemoryStream.Write(new[] { (byte)120, (byte)218 }, 0, 2); _zlibDeflater = new DeflateStream(_zlibMemoryStream, CompressionMode.Compress, false); } _zlibDeflater.Write(contents, 0, contents.Length); _zlibDeflater.Flush(); contents = _zlibMemoryStream.ToArray(); VncUtility.EncodeUInt32BE(contents, 0, (uint)(contents.Length - 4)); AddRegion(region, VncEncoding.Zlib, contents); } else #endif { this.AddRegion(region, VncEncoding.Raw, contents); } }
/// <summary> /// Initializes a new instance of the <see cref="FramebufferUpdateRequest"/> class. /// </summary> /// <param name="incremental">Whether an incremental update is desired.</param> /// <param name="region">The region to update.</param> public FramebufferUpdateRequest(bool incremental, VncRectangle region) { this.Incremental = incremental; this.Region = region; }
/// <summary> /// <para> /// Copies pixels. A format conversion is performed if necessary. /// </para> /// <para> /// Be sure to lock <see cref="VncFramebuffer.SyncRoot"/> first to avoid tearing, /// if the connection is active. /// </para> /// </summary> /// <param name="source">A pointer to the upper-left corner of the source.</param> /// <param name="sourceStride">The offset in the source between one Y coordinate and the next.</param> /// <param name="sourceFormat">The source pixel format.</param> /// <param name="sourceRectangle">The rectangle in the source to decode.</param> /// <param name="target">A pointer to the upper-left corner of the target.</param> /// <param name="targetStride">The offset in the target between one Y coordinate and the next.</param> /// <param name="targetFormat">The target pixel format.</param> /// <param name="targetX">The X coordinate in the target that the leftmost pixel should be placed into.</param> /// <param name="targetY">The Y coordinate in the target that the topmost pixel should be placed into.</param> public static unsafe void Copy( IntPtr source, int sourceStride, VncPixelFormat sourceFormat, VncRectangle sourceRectangle, IntPtr target, int targetStride, VncPixelFormat targetFormat, int targetX = 0, int targetY = 0) { Throw.If.True(source == IntPtr.Zero, "source").True(target == IntPtr.Zero, "target"); Throw.If.Null(sourceFormat, "sourceFormat").Null(targetFormat, "targetFormat"); if (sourceRectangle.IsEmpty) { return; } int x = sourceRectangle.X, w = sourceRectangle.Width; int y = sourceRectangle.Y, h = sourceRectangle.Height; var sourceData = (byte*)(void*)source + (y * sourceStride) + (x * sourceFormat.BytesPerPixel); var targetData = (byte*)(void*)target + (targetY * targetStride) + (targetX * targetFormat.BytesPerPixel); if (sourceFormat.Equals(targetFormat)) { for (int iy = 0; iy < h; iy++) { if (sourceFormat.BytesPerPixel == 4) { uint* sourceDataX0 = (uint*)sourceData, targetDataX0 = (uint*)targetData; for (int ix = 0; ix < w; ix++) { *targetDataX0++ = *sourceDataX0++; } } else { int bytes = w * sourceFormat.BytesPerPixel; byte* sourceDataX0 = (byte*)sourceData, targetDataX0 = (byte*)targetData; for (int ib = 0; ib < bytes; ib++) { *targetDataX0++ = *sourceDataX0++; } } sourceData += sourceStride; targetData += targetStride; } } }
/// <summary> /// Responds to a <see cref="VncServerSession"/> update request. /// </summary> /// <param name="session"> /// The session on which the update request was received. /// </param> /// <returns> /// <see langword="true"/> if the operation completed successfully; otherwise, /// <see langword="false"/>. /// </returns> public unsafe bool RespondToUpdateRequest(IVncServerSession session) { VncRectangle subregion = default(VncRectangle); var fb = this.Framebuffer; var fbr = session.FramebufferUpdateRequest; if (fb == null || fbr == null) { return(false); } var incremental = fbr.Incremental; var region = fbr.Region; int bpp = fb.PixelFormat.BytesPerPixel; this.logger?.Log(LogLevel.Debug, () => $"Responding to an update request for region {region}."); session.FramebufferManualBeginUpdate(); // Take a lock here, as we will modify // both buffers heavily in the next block. lock (fb.SyncRoot) { lock (this.cachedFramebuffer.SyncRoot) { var actualBuffer = this.Framebuffer.GetBuffer(); var bufferedBuffer = this.cachedFramebuffer.GetBuffer(); // In this block, we will determine which rectangles need updating. Right now, we consider // each line at once. It's not a very efficient algorithm, but it works. // We're going to start at the upper-left position of the region, and then we will work our way down, // on a line by line basis, to determine if each line is still valid. // isLineInvalid will indicate, on a line-by-line basis, whether a line is still valid or not. for (int y = region.Y; y < region.Y + region.Height; y++) { subregion.X = region.X; subregion.Y = y; subregion.Width = region.Width; subregion.Height = 1; // For a given y, the x pixels are stored sequentially in the array // starting at y * stride (number of bytes per row); for each x // value there are bpp bytes of data (4 for a 32-bit integer); we are looking // for pixels between x and x + w so this translates to // y * stride + bpp * x and y * stride + bpp * (x + w) int srcOffset = (y * this.Framebuffer.Stride) + (bpp * region.X); int length = bpp * region.Width; var isValid = actualBuffer.AsSpan().Slice(srcOffset, length) .SequenceCompareTo(bufferedBuffer.AsSpan().Slice(srcOffset, length)) == 0; if (!isValid) { try { Buffer.BlockCopy(actualBuffer, srcOffset, bufferedBuffer, srcOffset, length); } catch { throw; } } this.isLineInvalid[y - region.Y] = !isValid; } } // lock } // lock if (incremental) { // Determine logical group of lines which are invalid. We find the first line which is invalid, // create a new region which contains the all invalid lines which immediately follow the current line. // If we find a valid line, we'll create a new region. int?y = null; for (int line = 0; line < region.Height; line++) { if (y == null && this.isLineInvalid[line]) { y = region.Y + line; } if (y != null && (!this.isLineInvalid[line] || line == region.Height - 1)) { // Flush subregion.X = region.X; subregion.Y = region.Y + y.Value; subregion.Width = region.Width; subregion.Height = line - y.Value + 1; session.FramebufferManualInvalidate(subregion); y = null; } } } else { session.FramebufferManualInvalidate(region); } return(session.FramebufferManualEndUpdate()); }
/// <summary> /// Writes a <see cref="VncRectangle"/> to the current position in the stream and advances the position within the stream by 8 bytes. /// </summary> /// <param name="region"> /// The <see cref="VncRectangle"/> to write to the stream. /// </param> public void SendRectangle(VncRectangle region) { var buffer = new byte[8]; VncUtility.EncodeUInt16BE(buffer, 0, (ushort)region.X); VncUtility.EncodeUInt16BE(buffer, 2, (ushort)region.Y); VncUtility.EncodeUInt16BE(buffer, 4, (ushort)region.Width); VncUtility.EncodeUInt16BE(buffer, 6, (ushort)region.Height); this.Send(buffer); }
/// <summary> /// Responds to a <see cref="VncServerSession"/> update request. /// </summary> /// <param name="session"> /// The session on which the update request was received. /// </param> /// <returns> /// <see langword="true"/> if the operation completed successfully; otherwise, /// <see langword="false"/>. /// </returns> public unsafe bool RespondToUpdateRequest(VncServerSession session) { VncRectangle subregion = default(VncRectangle); var fb = this.Framebuffer; var fbr = session.FramebufferUpdateRequest; if (fb == null || fbr == null) { return(false); } var incremental = fbr.Incremental; var region = fbr.Region; int bpp = fb.PixelFormat.BytesPerPixel; session.FramebufferManualBeginUpdate(); // Take a lock here, as we will modify // both buffers heavily in the next block. lock (fb.SyncRoot) { lock (this.cachedFramebuffer.SyncRoot) { // Get the buffers (byte arrays) of data. We'll compare the data one pixel at a time. var actualBuffer = this.Framebuffer.GetBuffer(); var bufferedBuffer = this.cachedFramebuffer.GetBuffer(); for (int y = region.Y; y < region.Y + region.Height; y++) { subregion.X = region.X; subregion.Y = y; subregion.Width = region.Width; subregion.Height = 1; bool isValid = true; // For a given y, the x pixels are stored sequentially in the array // starting at y * stride (number of bytes per row); for each x // value there are bpp bytes of data (4 for a 32-bit integer); we are looking // for pixels between x and x + w so this translates to // y * stride + bpp * x and y * stride + bpp * (x + w) int srcOffset = (y * this.Framebuffer.Stride) + (bpp * region.X); int length = bpp * region.Width; fixed(byte *actualLinePtr = actualBuffer, bufferedLinePtr = bufferedBuffer) { isValid = NativeMethods.memcmp(actualLinePtr + srcOffset, bufferedLinePtr + srcOffset, (uint)length) == 0; } if (!isValid) { Buffer.BlockCopy(actualBuffer, srcOffset, bufferedBuffer, srcOffset, length); } this.isLineInvalid[y - region.Y] = !isValid; } } // lock } // lock if (incremental) { int?y = null; for (int line = 0; line < region.Height; line++) { if (y == null && this.isLineInvalid[line]) { y = region.Y + line; } if (y != null && (!this.isLineInvalid[line] || line == region.Height)) { // Flush subregion.X = region.X; subregion.Y = region.Y + y.Value; subregion.Width = region.Width; subregion.Height = line - y.Value; session.FramebufferManualInvalidate(subregion); y = null; } } } else { session.FramebufferManualInvalidate(region); } return(session.FramebufferManualEndUpdate()); }
/// <inheritdoc/> public override void Send(Stream stream, VncPixelFormat pixelFormat, VncRectangle region, byte[] contents) { stream.Write(contents, 0, contents.Length); }
/// <summary> /// Copies pixels between two byte arrays. A format conversion is performed if necessary. /// /// Be sure to lock <see cref="VncFramebuffer.SyncRoot"/> first to avoid tearing, /// if the connection is active. /// </summary> /// <param name="source">A pointer to the upper-left corner of the source.</param> /// <param name="sourceWidth">The width of the source image.</param> /// <param name="sourceStride">The offset in the source between one Y coordinate and the next.</param> /// <param name="sourceFormat">The source pixel format.</param> /// <param name="sourceRectangle">The rectangle in the source to decode.</param> /// <param name="target">A pointer to the upper-left corner of the target.</param> /// <param name="targetWidth">The width of the target image.</param> /// <param name="targetStride">The offset in the target between one Y coordinate and the next.</param> /// <param name="targetFormat">The target pixel format.</param> /// <param name="targetX">The X coordinate in the target that the leftmost pixel should be placed into.</param> /// <param name="targetY">The Y coordinate in the target that the topmost pixel should be placed into.</param> public static unsafe void Copy( byte[] source, int sourceWidth, int sourceStride, VncPixelFormat sourceFormat, VncRectangle sourceRectangle, byte[] target, int targetWidth, int targetStride, VncPixelFormat targetFormat, int targetX = 0, int targetY = 0) { Throw.If.Null(source, "source").Null(target, "target"); if (sourceRectangle.IsEmpty) { return; } int x = sourceRectangle.X, w = sourceRectangle.Width; int y = sourceRectangle.Y, h = sourceRectangle.Height; if (sourceFormat.Equals(targetFormat)) { if (sourceRectangle.Width == sourceWidth && sourceWidth == targetWidth && sourceStride == targetStride) { int sourceStart = sourceStride * y; int length = targetStride * h; Buffer.BlockCopy(source, sourceStart, target, 0, length); } else { for (int iy = 0; iy < h; iy++) { int sourceStart = (sourceStride * (iy + y)) + (x * sourceFormat.BitsPerPixel / 8); int targetStart = targetStride * iy; int length = w * sourceFormat.BitsPerPixel / 8; Buffer.BlockCopy(source, sourceStart, target, targetStart, length); } } } }
/// <summary> /// Sends a rectangle using JPEG compression. /// </summary> /// <param name="stream"> /// The <see cref="Stream"/> which represents connectivity with the client. /// </param> /// <param name="pixelFormat"> /// The pixel format to use. This must be <see cref="VncPixelFormat.RGB32"/>. /// </param> /// <param name="region"> /// The rectangle to send. /// </param> /// <param name="contents"> /// A buffer holding the raw pixel data for the rectangle. /// </param> /// <param name="jpegQualityLevel"> /// The JPEG quality level to use. /// </param> protected int SendWithJpegCompression(Stream stream, VncPixelFormat pixelFormat, VncRectangle region, byte[] contents, int jpegQualityLevel) { var subsamplingOption = TJSubsamplingOption.Chrominance420; var size = this.compressor.GetBufferSize(region.Width, region.Height, subsamplingOption) + 5; byte[] buffer = null; try { // The first 5 bytes will hold the compression control byte and the size of the // JPEG buffer. buffer = ArrayPool <byte> .Shared.Rent(size); var qualityLevel = GetQualityLevel(this.VncServerSession); var jpeg = this.compressor.Compress( contents.AsSpan(), buffer.AsSpan(5), 0, /* auto-calculate pitch */ region.Width, region.Height, TJPixelFormat.BGRA, subsamplingOption, jpegQualityLevel, TJFlags.NoRealloc); // Write the JPEG compression control byte and the size of the JPEG buffer. buffer[0] = (byte)TightCompressionControl.JpegCompression; var length = WriteEncodedValue(buffer, 1, jpeg.Length); stream.Write(buffer, 0, length); stream.Write(buffer, 5, jpeg.Length); return(jpeg.Length + 5); } finally { if (buffer != null) { ArrayPool <byte> .Shared.Return(buffer); } } }
/// <summary> /// Sends the contents of a rectangle to the client. /// </summary> /// <param name="stream"> /// A <see cref="Stream"/> which represents the connection to the VNC client. /// </param> /// <param name="pixelFormat"> /// The <see cref="VncPixelFormat"/> being used. /// </param> /// <param name="region"> /// The dimesions of the rectangle. /// </param> /// <param name="contents"> /// The contents of the rectangle, in raw pixel format. /// </param> public abstract void Send(Stream stream, VncPixelFormat pixelFormat, VncRectangle region, byte[] contents);
/// <summary> /// Sends a rectangle using basic compression. /// </summary> /// <param name="stream"> /// The <see cref="Stream"/> which represents connectivity with the client. /// </param> /// <param name="pixelFormat"> /// The pixel format to use. /// </param> /// <param name="region"> /// The rectangle to send. /// </param> /// <param name="contents"> /// A buffer holding the raw pixel data for the rectangle. /// </param> protected int SendWithBasicCompression(Stream stream, VncPixelFormat pixelFormat, VncRectangle region, byte[] contents) { if (contents.Length < 12) { var compressionControl = TightCompressionControl.BasicCompression; stream.WriteByte((byte)compressionControl); // If the data size after applying the filter but before the compression is less then 12, then the data is sent as is, uncompressed. byte[] encodedBuffer = new byte[4]; var length = WriteEncodedValue(encodedBuffer, 0, (int)contents.Length); stream.Write(encodedBuffer, 0, length); stream.Write(contents, 0, contents.Length); return(1 + length + contents.Length); } else { var compressionControl = TightCompressionControl.BasicCompression | TightCompressionControl.ResetStream0 | TightCompressionControl.UseStream0; var compressionLevel = GetCompressionLevel(this.VncServerSession); stream.WriteByte((byte)compressionControl); using (var buffer = new MemoryStream()) using (var deflater = new ZlibStream(buffer, CompressionMode.Compress, compressionLevel)) { // The Tight encoding makes use of a new type TPIXEL (Tight pixel). This is the same as a PIXEL for the agreed // pixel format, except where true-colour-flag is non-zero, bits-per-pixel is 32, depth is 24 and all of the bits // making up the red, green and blue intensities are exactly 8 bits wide. // In this case a TPIXEL is only 3 bytes long, where the first byte is the red component, the second byte is the // green component, and the third byte is the blue component of the pixel color value. if (pixelFormat.BitsPerPixel == 32 && pixelFormat.BitDepth == 24 && pixelFormat.BlueBits == 8 && pixelFormat.RedBits == 8 && pixelFormat.GreenBits == 8 && !pixelFormat.IsPalettized) { Debug.Assert(contents.Length % 4 == 0, "The size of the raw pixel data must be a multiple of 4 when using a 32bpp pixel format."); int redOffset = pixelFormat.RedShift / 8; int blueOffset = pixelFormat.BlueShift / 8; int greenOffset = pixelFormat.GreenShift / 8; for (int i = 0; i < contents.Length; i += 4) { if (i == contents.Length - 4) { deflater.FlushMode = FlushType.Full; } // The first byte is the red component, the second byte is the // green component, and the third byte is the blue component of the pixel color value. deflater.Write(contents, i + redOffset, 1); deflater.Write(contents, i + greenOffset, 1); deflater.Write(contents, i + blueOffset, 1); } } else { deflater.FlushMode = FlushType.Finish; deflater.Write(contents, 0, contents.Length); } deflater.Flush(); byte[] encodedBuffer = new byte[4]; var length = WriteEncodedValue(encodedBuffer, 0, (int)buffer.Length); stream.Write(encodedBuffer, 0, length); buffer.Position = 0; buffer.CopyTo(stream); return((int)buffer.Length + 1); } } }