I am trying to screen capture a rectangle based on the X,Y position of the mouse. I have a couple of issues with my code:
1.)I want the mouse cursor to be at the center of the rectangle(I don't need the cursor in the screen shot).
2.)The screenshots look ok on the first monitor but on the second monitor the screen shots seem to be offset. The reason it is offset is because monitor #2 has Scale And Layout setting of 125%.
Any help with this would be appreciated thanks.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows;
using System.Drawing;
using System.Drawing.Imaging;
namespace Test
{
public static class MouseScreenCapture
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(out POINT pt);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public static implicit operator System.Drawing.Point(POINT point)
{
return new System.Drawing.Point(point.X, point.Y);
}
}
static void Main(string[] args)
{
Start();
}
private static System.Threading.Timer _timer = null;
public static void Start()
{
_timer = new System.Threading.Timer(TimerCallback, null, 0, 5000);
}
private static void TimerCallback(Object o)
{
GetMousePositionAndScreenCapture();
}
public static void GetMousePositionAndScreenCapture()
{
POINT lpPoint ;
GetCursorPos(out lpPoint);
System.Drawing.Size sz = new System.Drawing.Size();
sz.Height = 100;
sz.Width = 100;
var bounds = new Rectangle(lpPoint, sz);
var image = new Bitmap(bounds.Width, bounds.Height);
using (var graphics = Graphics.FromImage(image))
{
graphics.CopyFromScreen(new System.Drawing.Point(bounds.Left, bounds.Top), System.Drawing.Point.Empty, bounds.Size);
}
image.Save(#"C:\temp\newss.jpeg", ImageFormat.Jpeg);
}
}
}
As far as issue one, where you want the arrow cursor to be at the center of screenshot, would appear fairly straight forward. If the size of the screenshot is 100x100 then it would appear that when you get the Point of the cursor that you would want to move the X value -50 and the Y value -50. Basically half the width and height of the screenshot rectangle size. This would put the cursor at the center of the screenshot rectangle.
The only thing you may want to look for is if the cursor’s X or Y positions are less than 50, in which case we would simply set the value to 0. Adding these changes to lpPoint should fix this. Something like…
lpPoint.X = lpPoint.X >= 50 ? lpPoint.X - 50 : 0;
lpPoint.Y = lpPoint.Y >= 50 ? lpPoint.Y - 50 : 0;
For the second issue I can only assume this may have something to do with possibly different resolutions of the monitors. I would think it would still work, as it appears to work in my small tests. However, this is speculation on my part. Can you show a screen shot of both monitors showing what you are describing?
Related
Hi so like the title says, i'm trying to make an image Viewer for a project ( a bit in the kind of Windows Image viewer). I saw a lot of code displaying their RenderForm into a RenderLoop, but I don't like this solution since I don't want to refresh the image infinitely in the RenderLoop. I want to call the Draw method only when I need to redraw(on a zoom by example.) The problem is, now I've tried to use renderLoop.Show() but it does not stay on the screen and close right after all code has been executed...
using SharpDX;
using SharpDX.Windows;
using SharpDX.D3DCompiler;
using SharpDX.Direct2D1;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Mathematics;
using System.Drawing;
using System.Windows.Forms;
using Device = SharpDX.Direct3D11.Device;
using Color = SharpDX.Color;
namespace SharpDXWic
{
public class SharpDXDisplay : IDisposable
{
private const int WIDTH = 1500;
private const int HEIGHT = 800;
private Device device;
private SwapChain swapChain;
private RenderForm renderForm;
private RenderTargetView targetView;
public SharpDXDisplay(string display_title)
{
renderForm = new RenderForm(display_title);
renderForm.Width = WIDTH;
renderForm.Height = HEIGHT;
Texture2D target;
SwapChainDescription scd = new SwapChainDescription()
{
BufferCount = 1,
Flags = SwapChainFlags.None,
IsWindowed = true,
ModeDescription = new ModeDescription(WIDTH,HEIGHT, new Rational(60, 1),Format.R8G8B8A8_UNorm),
OutputHandle = renderForm.Handle,
SampleDescription = new SampleDescription(1, 0),
SwapEffect = SwapEffect.Discard,
Usage = Usage.RenderTargetOutput
};
Device.CreateWithSwapChain( SharpDX.Direct3D.DriverType.Hardware,DeviceCreationFlags.Debug, scd, out device, out swapChain);
target = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);
targetView = new RenderTargetView(device, target);
device.ImmediateContext.OutputMerger.SetRenderTargets(targetView);
renderForm.Show();
device.ImmediateContext.ClearRenderTargetView(targetView, Color.CornflowerBlue);
swapChain.Present(0, PresentFlags.None);
}
private void OnClosing()
{
Dispose();
}
public void Dispose()
{
device.Dispose();
swapChain.Dispose();
renderForm.Dispose();
targetView.Dispose();
}
}
}
My goal here would be to Draw() a form without entering the RenderLoop. I only want to refresh the image on-demand and not refresh it constantly.
It is in fact possible to display an image into a Windows Form without using an infinite loop. Adding action like zooming or resizing to your form and perform action (such has redraw your image) works perfectly.
Of course, if you are making a video game, that is not the best solution because you want constant render of your graphics. However, for an image viewer, it is perfectly fine to draw everything to a windows form only once and update it on ActionEvents.
Coding my project was going well. But today I noticed a problem.
My main notebook has full hd (1920x1080) resolution and I am coding my projects here. When I changed my main notebook's resolution to 1280x1024, 1280x800, or 1024x768 there is no problem. My application's resolution is 1024x768 and it is not collapsing. This is the printscreen.
But my other notebook has 1366x768 resolution. And I am running my application on this notebook. Oh! There is a disappointment. My application screen shifted. This is the bad printscreen.
I do not understand why. What can I do correcting this error?
It arises from different DPI settings. You can do this in the form load:
// Get DPI width
float x = this.CreateGraphics().DpiX;
// If screen is width
if (x == 120)
// Get big image from Resources
this.BackgroundImage = Properties.Resources.BigImage;
else
// Get small image from Resources
this.BackgroundImage = Properties.Resources.SmallImage;
I had the same problem with a Windows Forms form. All I had to do was change the AutoScaleMode setting for the form from Font to DPI and change the FormBorderStylefor setting from Fixed to Sizable. Now the Windows Forms form displays correctly on the desktop and the laptop.
You can check if the DPI settings of the two screens are the same. You do this by going through Control Panel or Display options (I can't remember exactly, and I don't have Windows 7 in front of me) (You probably have 120 DPI on your HD capable laptop, and standard 96 on the other).
In your program, set the form's AutoScaleMode to None and try again.
Here is a resource to assist in how to handle auto-scaling forms:
Automatic scaling in Windows Forms
There is no direct solution to fix the pixel problem. But we can do it ourselves.
First we have to find the controls available in our form, and then we have to resize them.
Add a class, "IdentifyControls.cs", that identifies all the controls of a form and returns the list of control in your application. A class can be added into the application selecting project -> Add class from the menu bar. Then type
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace FallingFlowers // Falling Flowers is the name of my project
{
public static class IdentifyControl
{
public static List<Control> findControls(Control c)
{
List<Control> list = new List<Control>();
foreach (Control control in c.Controls)
list.Add(control);
return list;
}
}
}
Then add another class into your application, say "demo.cs":
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
namespace FallingFlowers // Falling Flowers is the name of my project
{
public class demo
{
public static void relocate(Form fo, int ox, int oy)
{
List<Control> list = new List<Control>();
list = IdentifyControl.findControls(fo);
for (int i = 0; i < list.Count; i++)
reposition(list[i], ox, oy);
}
public static void reposition(Control c, int ox, int oy)
{
int x, y;
x = ((c.Location.X * Screen.PrimaryScreen.Bounds.Width) / ox);
y = ((c.Location.Y * Screen.PrimaryScreen.Bounds.Height) / oy);
c.Location = new Point(x, y);
x = ((c.Width * Screen.PrimaryScreen.Bounds.Width) / ox);
y = ((c.Height * Screen.PrimaryScreen.Bounds.Height) / oy);
c.Width = x;
c.Height = y;
if (c is Label || c is Button)
resizeText(c, ox, oy);
}
public static void resizeText(Control l, int ox, int oy)
{
float s;
float txtsize = l.Font.Size;
s = ((txtsize * Screen.PrimaryScreen.Bounds.Width) / ox)+1;
l.Font = new Font(l.Font.Name, s,l.Font.Style);
}
public static void repositionForm(Form f, int ox, int oy)
{
int x, y;
x = (f.Location.X * Screen.PrimaryScreen.Bounds.Width) / ox;
y = (f.Location.Y * Screen.PrimaryScreen.Bounds.Width) / oy;
f.Location = new Point(x, y);
}
}
}
This class contains methods to relocate the controls, resize the text and to resize the form.
Call these functions in the load event of your form.
To relocate all the controls in the form
demo.relocate(this, 1366, 768);
Here 1366 and 768 are the original resolution in which the application is developed.
To relocate the form:
demo.repositionForm(this, 1366, 768);
1366 and 768 are the original resolution in which the application is developed.
For you it will be demo.relocate(this, 1920, 1080);.
I hope that this will help you :-)...
I agree the code for the screen settings would help a lot in finding out your issue.
But it seems like you're setting the pictures to set coordinate points instead of points relative to the screen size. You might want to make the coordinates a ratio of the screen size to have the display always look nice.
Recently I found a bug while drawing stuff with metafiles. Right now I am not sure if I am doing something wrong or if there is a bug within the drawing of metafiles itself:
While drawing images on a metafile which is drawn on another metafile itself by PlayEnhMetafile I lose images far down or to the right. I guess it has something to do with screen coordinates (I run dual screen 1280*1024, so 2560*1024), 'cause the bottom lane where the images begin to vanish is around 500.
Here is some example-code I created to show you the problem more specifically. (You can just replace the Form1.cs of a freshly created Windows C# project with this code and place a button on it)
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace MetaFileDrawing
{
public partial class Form1
: Form
{
[DllImport("gdi32.dll", SetLastError = true)]
private static extern bool PlayEnhMetaFile(IntPtr hdc, IntPtr hEmf, ref Rectangle rectangle);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern bool DeleteObject(IntPtr hGdiObj);
public Form1()
{
InitializeComponent();
}
/// <summary>
/// Creates the sub-metafile where the actual drawing is done (and the problems occur).
/// </summary>
private Metafile GetSubMetafile()
{
Metafile metafile = null;
using(Graphics controlGraphics = this.CreateGraphics())
{
using(MemoryStream memoryStream = new MemoryStream())
{
metafile = new Metafile(memoryStream, controlGraphics.GetHdc(), EmfType.EmfOnly, string.Empty);
using(Graphics metafileGraphics = Graphics.FromImage(metafile))
{
Bitmap bitmap = new Bitmap("Fibonacci.png");
// Draw the image 3 times... if pushed to far down, it wont show up?
metafileGraphics.DrawRectangle(Pens.Yellow, new Rectangle(0, 0, 40, 1200));
metafileGraphics.DrawImage(bitmap, new Point(0, 0));
metafileGraphics.DrawImage(bitmap, new Point(10, 950));
metafileGraphics.DrawImage(bitmap, new Point(20, 1150));
}
}
controlGraphics.ReleaseHdc();
}
return metafile;
}
/// <summary>
/// Creates and draws the metafile.
/// </summary>
private void DrawMetafile()
{
using(Graphics controlGraphics = this.CreateGraphics())
{
using(MemoryStream memoryStream = new MemoryStream())
{
// EmfType.EmfOnly is a restriction defined by my project limitations
Metafile metafile = new Metafile(memoryStream, controlGraphics.GetHdc(), EmfType.EmfOnly, string.Empty);
using(Graphics metafileGraphics = Graphics.FromImage(metafile))
{
// A large red rect for orientation
metafileGraphics.DrawRectangle(Pens.Red, new Rectangle(0, 0, 4000, 4000));
// Create the sub metafile
Metafile subMetafile = GetSubMetafile();
// To check, draw the subMetafile with DrawImage (works fine, the inlined image is drawn 3 times)
metafileGraphics.DrawImage(subMetafile, new Point(10, 0));
// On the right side, draw the sub metafile using PlayEnhMetaFile (dont work correctly, only 2 images)
IntPtr hMetafile = subMetafile.GetHenhmetafile();
Rectangle rectangle1 = new Rectangle(100, 0, 170, 1230);
PlayEnhMetaFile(metafileGraphics.GetHdc(), hMetafile, ref rectangle1);
metafileGraphics.ReleaseHdc();
DeleteObject(hMetafile);
}
metafile.Save("Output.emf");
}
controlGraphics.ReleaseHdc();
}
}
private void button1_Click(object sender, EventArgs e)
{
DrawMetafile();
}
}
}
As you can see, using the PlayEnhMetaFile function causes me to lose one of the three images. Any ideas?
I ran into the same problem using C++, and found two workarounds. I could use GDI+ to attach to the metafile and play it, but the coordinates were slightly off. The more complicated alternative which I am currently using is using EnumEnhMetaFile, and manually doing the bitblt/stretchblt/stretchdibits calls, this seems to work. If you found a better solution let me know.
Im trying to write an app that takes a screen shot every 40ms and saves it to disk, im getting an error in GDI
Anybody know if im being too adventurous trying to save a screen shot as jpeg every 40ms or at a rate of 25 fps?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
namespace ScreenRecorder
{
class Program
{
private static System.Timers.Timer screenTimer;
private static int screenNumber;
static void Main(string[] args)
{
screenTimer = new System.Timers.Timer(40);
// Hook up the Elapsed event for the timer.
screenTimer.Elapsed += new System.Timers.ElapsedEventHandler(screenTimer_Elapsed);
screenTimer.Enabled = true;
Console.Read();
}
static void screenTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
//get the screen
Image myImage = CaptureScreen();
myImage.Save(#"C:\stuff\Development\ScreenRecorder\ScreenImages\img" + screenNumber + ".jpg");
myImage.Dispose();
screenNumber++;
}
private static Image CaptureScreen()
{
Rectangle screenSize = Screen.PrimaryScreen.Bounds;
Bitmap target = new Bitmap(screenSize.Width, screenSize.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.CopyFromScreen(0, 0, 0, 0, new Size(screenSize.Width, screenSize.Height));
}
return target;
}
}
}
GDI+ is not reentrant nor multithreadable (what-a-word). You are probably getting (your secret error) because two different threads are competing for GDI+ resources.
From System.Timers.Timer documentation:
The server-based Timer is designed for use with worker threads in a multithreaded environment. Server timers can move among threads to handle the raised Elapsed event, resulting in more accuracy than Windows timers in raising the event on time.
Use Forms.Timer to serialize your capturing to message queue of the form, you'll get much better chance of not breaking it. You might get what you want - but - use PNG instead of JPG. It will be more efficient for 'normal' forms.
Multithreading + GDI+ = big NO NO.
Also - you won't get your pictures at regular interval - but you don't have to worry about that since any change what can expected is to occur from the message loop also - so you won't miss a bit.
Yeah, that's going to blow. The timer's Elapsed method will run on a threadpool thread, regardless whether the previous one has finished. Your screen capture + bitmap save is bound to take longer than 40 milliseconds. Sooner or later, probably sooner, two Elapsed handlers are going to run concurrently, using the same screenNumber variable value. Kaboom on not being able to overwrite a locked file.
You'll need to use a System.Threading.Timer instead. Start it with a period of 0 so the callback only runs once. After the screenshot, restart the timer again. Now you can be sure that you'll never get more than one thread running at the same time.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.Diagnostics;
namespace ScreenRecorder
{
class Program
{
private static System.Timers.Timer screenTimer;
private static int screenNumber;
private static Mutex m = new Mutex();
static void Main(string[] args)
{
screenTimer = new System.Timers.Timer(40);
// Hook up the Elapsed event for the timer.
screenTimer.Elapsed += new System.Timers.ElapsedEventHandler(screenTimer_Elapsed);
screenTimer.Enabled = true;
Console.Read();
}
static void screenTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Thread t = new Thread(()=>SaveImage());
t.Start();
}
private static void SaveImage()
{
m.WaitOne();
Image myImage = CaptureScreen();
myImage.Save(#"C:\stuff\Development\ScreenRecorder\ScreenImages\img" + screenNumber + ".png");
myImage.Dispose();
screenNumber++;
m.ReleaseMutex();
}
private static Image CaptureScreen()
{
Rectangle screenSize = Screen.PrimaryScreen.Bounds;
Bitmap target = new Bitmap(screenSize.Width, screenSize.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.CopyFromScreen(0, 0, 0, 0, new Size(screenSize.Width, screenSize.Height));
}
return target;
}
}
}
This question has been asked for other languages, and even for those other languages, I have found their answers lacking in how to exactly do it, cleanly (no messed up screen repaints, etc..).
Is it possible to draw onto the Windows Desktop from C#? I am looking for an example if possible.
Try the following:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
class Program {
[DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("User32.dll")]
static extern int ReleaseDC(IntPtr hwnd, IntPtr dc);
static void Main(string[] args) {
IntPtr desktop = GetDC(IntPtr.Zero);
using (Graphics g = Graphics.FromHdc(desktop)) {
g.FillRectangle(Brushes.Red, 0, 0, 100, 100);
}
ReleaseDC(IntPtr.Zero, desktop);
}
}
You can try:
Graphics.FromHwnd(IntPtr.Zero)
You can see a real-world code example within https://uiautomationverify.codeplex.com/SourceControl/latest#UIAVerify/Tools/visualuiverify/utils/screenrectangle.cs
This draws a rectangle that will appear on the screen until the user chooses to remove it at an arbitrary position (wont be repainted over). It uses a windows form thats hidden/ appears as a popup.
This is the code behind the UIAVerify.exe tool in the current Windows SDK.
If you want to use the above, copy the following files into your project:
utils\screenboundingrectangle.cs
utils\screenrectangle.cs
win32\*
Might need to update namespaces accordingly + add references to System.Drawing + System.Windows.Forms
Then you can draw a rectangle with the following code:
namespace Something
{
public class Highlighter
{
ScreenBoundingRectangle _rectangle = new ScreenBoundingRectangle();
public void DrawRectangle(Rectangle rect)
{
_rectangle.Color = System.Drawing.Color.Red;
_rectangle.Opacity = 0.8;
_rectangle.Location = rect;
this._rectangle.Visible = true;
}
}
}
and
var rect = Rectangle.FromLTRB(100, 100, 100, 100);
var hi = new Highlighter();
hi.DrawRectangle(rect);