/// <summary> /// Paint the actual visible parts /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnPaint(object sender, PaintEventArgs e) { var graphics = e.Graphics; var clipRectangle = e.ClipRectangle; graphics.DrawImageUnscaled(_capture.Bitmap, Point.Empty); // Only draw Cursor if it's (partly) visible if (_capture.Cursor != null && _capture.CursorVisible && clipRectangle.IntersectsWith(new Rectangle(_capture.CursorLocation, _capture.Cursor.Size))) { graphics.DrawIcon(_capture.Cursor, _capture.CursorLocation.X, _capture.CursorLocation.Y); } if (_mouseDown || UsedCaptureMode == CaptureMode.Window || IsAnimating(_windowAnimator)) { _captureRect = _captureRect.Intersect(new Rectangle(Point.Empty, _capture.ScreenBounds.Size)); // crop what is outside the screen var fixedRect = IsAnimating(_windowAnimator) ? _windowAnimator.Current : _captureRect; // If the _windowScroller != null, we can (most likely) capture the window with a scrolling technique if (WindowScroller != null && Equals(WindowScroller.ScrollBarWindow, SelectedCaptureWindow)) { graphics.FillRectangle(ScrollingOverlayBrush, fixedRect); } else { graphics.FillRectangle(GreenOverlayBrush, fixedRect); } graphics.DrawRectangle(OverlayPen, fixedRect); // rulers const int dist = 8; string captureWidth; string captureHeight; // The following fixes the very old incorrect size information bug if (UsedCaptureMode == CaptureMode.Window) { captureWidth = _captureRect.Width.ToString(CultureInfo.InvariantCulture); captureHeight = _captureRect.Height.ToString(CultureInfo.InvariantCulture); } else { captureWidth = (_captureRect.Width + 1).ToString(CultureInfo.InvariantCulture); captureHeight = (_captureRect.Height + 1).ToString(CultureInfo.InvariantCulture); } using (var rulerFont = new Font(FontFamily.GenericSansSerif, 8)) { var measureWidth = TextRenderer.MeasureText(captureWidth, rulerFont); var measureHeight = TextRenderer.MeasureText(captureHeight, rulerFont); var hSpace = measureWidth.Width + 3; var vSpace = measureHeight.Height + 3; Brush bgBrush = new SolidBrush(Color.FromArgb(200, 217, 240, 227)); var rulerPen = new Pen(Color.SeaGreen); // horizontal ruler if (fixedRect.Width > hSpace + 3) { using (var p = RoundedRectangle.Create2( fixedRect.X + (fixedRect.Width / 2 - hSpace / 2) + 3, fixedRect.Y - dist - 7, measureWidth.Width - 3, measureWidth.Height, 3)) { graphics.FillPath(bgBrush, p); graphics.DrawPath(rulerPen, p); graphics.DrawString(captureWidth, rulerFont, rulerPen.Brush, fixedRect.X + (fixedRect.Width / 2 - hSpace / 2) + 3, fixedRect.Y - dist - 7); graphics.DrawLine(rulerPen, fixedRect.X, fixedRect.Y - dist, fixedRect.X + (fixedRect.Width / 2 - hSpace / 2), fixedRect.Y - dist); graphics.DrawLine(rulerPen, fixedRect.X + fixedRect.Width / 2 + hSpace / 2, fixedRect.Y - dist, fixedRect.X + fixedRect.Width, fixedRect.Y - dist); graphics.DrawLine(rulerPen, fixedRect.X, fixedRect.Y - dist - 3, fixedRect.X, fixedRect.Y - dist + 3); graphics.DrawLine(rulerPen, fixedRect.X + fixedRect.Width, fixedRect.Y - dist - 3, fixedRect.X + fixedRect.Width, fixedRect.Y - dist + 3); } } // vertical ruler if (fixedRect.Height > vSpace + 3) { using (var p = RoundedRectangle.Create2( fixedRect.X - measureHeight.Width + 1, fixedRect.Y + (fixedRect.Height / 2 - vSpace / 2) + 2, measureHeight.Width - 3, measureHeight.Height - 1, 3)) { graphics.FillPath(bgBrush, p); graphics.DrawPath(rulerPen, p); graphics.DrawString(captureHeight, rulerFont, rulerPen.Brush, fixedRect.X - measureHeight.Width + 1, fixedRect.Y + (fixedRect.Height / 2 - vSpace / 2) + 2); graphics.DrawLine(rulerPen, fixedRect.X - dist, fixedRect.Y, fixedRect.X - dist, fixedRect.Y + (fixedRect.Height / 2 - vSpace / 2)); graphics.DrawLine(rulerPen, fixedRect.X - dist, fixedRect.Y + fixedRect.Height / 2 + vSpace / 2, fixedRect.X - dist, fixedRect.Y + fixedRect.Height); graphics.DrawLine(rulerPen, fixedRect.X - dist - 3, fixedRect.Y, fixedRect.X - dist + 3, fixedRect.Y); graphics.DrawLine(rulerPen, fixedRect.X - dist - 3, fixedRect.Y + fixedRect.Height, fixedRect.X - dist + 3, fixedRect.Y + fixedRect.Height); } } rulerPen.Dispose(); bgBrush.Dispose(); } // Display size of selected rectangle // Prepare the font and text. using (var sizeFont = new Font(FontFamily.GenericSansSerif, 12)) { // When capturing a Region we need to add 1 to the height/width for correction string sizeText; if (UsedCaptureMode == CaptureMode.Region) { // correct the GUI width to real width for the shown size sizeText = _captureRect.Width + 1 + " x " + (_captureRect.Height + 1); } else { sizeText = _captureRect.Width + " x " + _captureRect.Height; } // Calculate the scaled font size. var extent = graphics.MeasureString(sizeText, sizeFont); var hRatio = _captureRect.Height / (extent.Height * 2); var wRatio = _captureRect.Width / (extent.Width * 2); var ratio = hRatio < wRatio ? hRatio : wRatio; var newSize = sizeFont.Size * ratio; if (newSize >= 4) { // Only show if 4pt or larger. if (newSize > 20) { newSize = 20; } // Draw the size. using (var newSizeFont = new Font(FontFamily.GenericSansSerif, newSize, FontStyle.Bold)) { var sizeLocation = new PointF(fixedRect.X + _captureRect.Width / 2 - extent.Width / 2, fixedRect.Y + _captureRect.Height / 2 - newSizeFont.GetHeight() / 2); graphics.DrawString(sizeText, newSizeFont, Brushes.LightSeaGreen, sizeLocation); if (_showDebugInfo && SelectedCaptureWindow != null) { using (var process = Process.GetProcessById(SelectedCaptureWindow.GetProcessId())) { string title = $"#{SelectedCaptureWindow.Handle.ToInt64():X} - {(SelectedCaptureWindow.Text.Length > 0 ? SelectedCaptureWindow.Text : process.ProcessName)}"; var debugLocation = new PointF(fixedRect.X, fixedRect.Y); graphics.DrawString(title, sizeFont, Brushes.DarkOrange, debugLocation); } } } } } } else { using (var pen = new Pen(Color.LightSeaGreen)) { pen.DashStyle = DashStyle.Dot; var screenBounds = _capture.ScreenBounds; graphics.DrawLine(pen, _cursorPos.X, screenBounds.Y, _cursorPos.X, screenBounds.Height); graphics.DrawLine(pen, screenBounds.X, _cursorPos.Y, screenBounds.Width, _cursorPos.Y); } var xy = _cursorPos.X + " x " + _cursorPos.Y; using (var f = new Font(FontFamily.GenericSansSerif, 8)) { var xySize = TextRenderer.MeasureText(xy, f); using (var gp = RoundedRectangle.Create2(_cursorPos.X + 5, _cursorPos.Y + 5, xySize.Width - 3, xySize.Height, 3)) { using (Brush bgBrush = new SolidBrush(Color.FromArgb(200, 217, 240, 227))) { graphics.FillPath(bgBrush, gp); } using (var pen = new Pen(Color.SeaGreen)) { graphics.DrawPath(pen, gp); var coordinatePosition = new Point(_cursorPos.X + 5, _cursorPos.Y + 5); graphics.DrawString(xy, f, pen.Brush, coordinatePosition); } } } } // Zoom if (_zoomAnimator == null || (!IsAnimating(_zoomAnimator) && UsedCaptureMode == CaptureMode.Window)) { return; } const int zoomSourceWidth = 25; const int zoomSourceHeight = 25; var sourceRectangle = new NativeRect(_cursorPos.X - zoomSourceWidth / 2, _cursorPos.Y - zoomSourceHeight / 2, zoomSourceWidth, zoomSourceHeight); var destinationRectangle = _zoomAnimator.Current.Offset(_cursorPos); DrawZoom(graphics, sourceRectangle, destinationRectangle); }
/// <summary> /// update the frame, this only invalidates /// </summary> protected override void Animate() { var lastPos = _cursorPos; _cursorPos = _mouseMovePos; if (SelectedCaptureWindow != null && lastPos.Equals(_cursorPos) && !IsAnimating(_zoomAnimator) && !IsAnimating(_windowAnimator)) { return; } var lastWindow = SelectedCaptureWindow; var horizontalMove = false; var verticalMove = false; if (lastPos.X != _cursorPos.X) { horizontalMove = true; } if (lastPos.Y != _cursorPos.Y) { verticalMove = true; } if (UsedCaptureMode == CaptureMode.Region && _mouseDown) { _captureRect = new NativeRect(_cursorPos.X, _cursorPos.Y, _mX - _cursorPos.X, _mY - _cursorPos.Y).Normalize(); } // Iterate over the found windows and check if the current location is inside a window var cursorPosition = Cursor.Position; SelectedCaptureWindow = null; // Store the top window IInteropWindow selectedTopWindow = null; foreach (var window in _windows) { if (window.Handle == Handle) { // Ignore us continue; } if (!window.GetInfo().Bounds.Contains(cursorPosition)) { continue; } selectedTopWindow = window; SelectedCaptureWindow = window; // Only go over the children if we are in window mode if (CaptureMode.Window != UsedCaptureMode) { break; } // Find the child window which is under the mouse // Start with the parent, drill down var selectedChildWindow = window; // TODO: Limit the levels we go down? do { // Drill down, via the ZOrder var tmpChildWindow = selectedChildWindow .GetZOrderedChildren() .FirstOrDefault(interopWindow => interopWindow.GetInfo().Bounds.Contains(cursorPosition)); if (tmpChildWindow == null) { break; } selectedChildWindow = tmpChildWindow; } while (true); // Assign the found child window SelectedCaptureWindow = selectedChildWindow; break; } // Test if something changed if (SelectedCaptureWindow != null && !SelectedCaptureWindow.Equals(lastWindow)) { _capture.CaptureDetails.Title = selectedTopWindow.Text; _capture.CaptureDetails.AddMetaData("windowtitle", selectedTopWindow.Text); if (UsedCaptureMode == CaptureMode.Window) { // Recreate the WindowScroller, if this is enabled, so we can detect if we can scroll if (Conf.IsScrollingCaptureEnabled) { WindowScroller = SelectedCaptureWindow.GetWindowScroller(ScrollBarTypes.Vertical); if (WindowScroller == null) { foreach (var interopWindow in SelectedCaptureWindow.GetChildren()) { interopWindow.Dump(); } WindowScroller = SelectedCaptureWindow.GetChildren().Select(child => child.GetWindowScroller(ScrollBarTypes.Vertical)).FirstOrDefault(scroller => scroller != null); } } // We store the bound of the selected (child) window // If it's maximized we take the client-bounds, otherwise we have parts we should not copy. if (SelectedCaptureWindow.IsMaximized()) { _captureRect = SelectedCaptureWindow.GetInfo().ClientBounds; } else { _captureRect = SelectedCaptureWindow.GetInfo().Bounds; } // Make sure the bounds fit to it's parent, some windows are bigger than their parent // But only for non popups if (!SelectedCaptureWindow.GetInfo().Style.HasFlag(WindowStyleFlags.WS_POPUP)) { var parent = SelectedCaptureWindow.GetParent(); while (parent != IntPtr.Zero) { var parentWindow = InteropWindowFactory.CreateFor(parent); _captureRect = _captureRect.Intersect(parentWindow.GetInfo().Bounds); parent = parentWindow.GetParent(); } } // As the ClientRectangle is in screen coordinates and not in bitmap coordinates, we need to correct. _captureRect = _captureRect.Offset(-_capture.ScreenBounds.Location.X, -_capture.ScreenBounds.Location.Y); } } NativeRectFloat invalidateRectangle; if (_mouseDown && UsedCaptureMode != CaptureMode.Window) { var x1 = Math.Min(_mX, lastPos.X); var x2 = Math.Max(_mX, lastPos.X); var y1 = Math.Min(_mY, lastPos.Y); var y2 = Math.Max(_mY, lastPos.Y); x1 = Math.Min(x1, _cursorPos.X); x2 = Math.Max(x2, _cursorPos.X); y1 = Math.Min(y1, _cursorPos.Y); y2 = Math.Max(y2, _cursorPos.Y); // Safety correction x2 += 2; y2 += 2; // Here we correct for text-size // Calculate the size var textForWidth = Math.Max(Math.Abs(_mX - _cursorPos.X), Math.Abs(_mX - lastPos.X)); var textForHeight = Math.Max(Math.Abs(_mY - _cursorPos.Y), Math.Abs(_mY - lastPos.Y)); using (var rulerFont = new Font(FontFamily.GenericSansSerif, 8)) { var textWidth = TextRenderer.MeasureText(textForWidth.ToString(CultureInfo.InvariantCulture), rulerFont); x1 -= textWidth.Width + 15; var textHeight = TextRenderer.MeasureText(textForHeight.ToString(CultureInfo.InvariantCulture), rulerFont); y1 -= textHeight.Height + 10; } invalidateRectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1); Invalidate(invalidateRectangle); } else if (UsedCaptureMode != CaptureMode.Window) { var allScreenBounds = WindowCapture.GetScreenBounds(); allScreenBounds = allScreenBounds.MoveTo(WindowCapture.GetLocationRelativeToScreenBounds(allScreenBounds.Location)); if (verticalMove) { // Before invalidateRectangle = new NativeRect(allScreenBounds.Left, lastPos.Y - 2, Width + 2, 45).Normalize(); Invalidate(invalidateRectangle); // After invalidateRectangle = new NativeRect(allScreenBounds.Left, _cursorPos.Y - 2, Width + 2, 45).Normalize(); Invalidate(invalidateRectangle); } if (horizontalMove) { // Before invalidateRectangle = new NativeRect(lastPos.X - 2, allScreenBounds.Top, 75, Height + 2).Normalize(); Invalidate(invalidateRectangle); // After invalidateRectangle = new NativeRect(_cursorPos.X - 2, allScreenBounds.Top, 75, Height + 2).Normalize(); Invalidate(invalidateRectangle); } } else if (SelectedCaptureWindow != null && !SelectedCaptureWindow.Equals(lastWindow)) { // Window changed, animate from current to newly selected window _windowAnimator.ChangeDestination(_captureRect, FramesForMillis(700)); } // always animate the Window area through to the last frame, so we see the fade-in/out untill the end // Using a safety "offset" to make sure the text is invalidated too const int safetySize = 30; // Check if the animation needs to be drawn if (IsAnimating(_windowAnimator)) { invalidateRectangle = _windowAnimator.Current.Inflate(safetySize, safetySize); Invalidate(invalidateRectangle); invalidateRectangle = _windowAnimator.Next().Inflate(safetySize, safetySize); Invalidate(invalidateRectangle); // Check if this was the last of the windows animations in the normal region capture. if (UsedCaptureMode != CaptureMode.Window && !IsAnimating(_windowAnimator)) { Invalidate(); } } if (_zoomAnimator != null && (IsAnimating(_zoomAnimator) || UsedCaptureMode != CaptureMode.Window)) { // Make sure we invalidate the old zoom area invalidateRectangle = _zoomAnimator.Current.Offset(lastPos); Invalidate(invalidateRectangle); // Only verify if we are really showing the zoom, not the outgoing animation if (Conf.ZoomerEnabled && UsedCaptureMode != CaptureMode.Window) { VerifyZoomAnimation(_cursorPos, false); } // The following logic is not needed, next always returns the current if there are no frames left // but it makes more sense if we want to change something in the logic invalidateRectangle = IsAnimating(_zoomAnimator) ? _zoomAnimator.Next() : _zoomAnimator.Current; Invalidate(invalidateRectangle.Offset(_cursorPos)); } // Force update "now" Update(); }