Generate BitmapSource from UIElement - c#

I am attempting to generate a BitmapFrame that is based on a UIElement. Here is my function:
private BitmapFrame RenderToBitmap2()
{
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(200, 200, 96, 96, PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
VisualBrush aVisualBrush = new VisualBrush(GenerateTestStackPanel());
drawingContext.DrawRectangle(aVisualBrush, new Pen(Brushes.Green, 2), new Rect(new Size(150, 150)));
drawingContext.Close();
renderBitmap.Render(drawingVisual);
return BitmapFrame.Create(renderBitmap);
}
For testing and debugging purposes, I am using an additional function that creates a simple StackFrame that should create a valid visual element that can be represented:
private StackPanel GenerateTestStackPanel()
{
// Create a red Ellipse.
Ellipse myEllipse = new Ellipse();
myEllipse.Fill = Brushes.Green;
myEllipse.StrokeThickness = 2;
myEllipse.Stroke = Brushes.Black;
// Set the width and height of the Ellipse.
myEllipse.Width = 200;
myEllipse.Height = 200;
// Add the Ellipse to the StackPanel.
StackPanel myStackPanel = new StackPanel();
myStackPanel.Children.Add(myEllipse);
return myStackPanel;
}
For some reason, the VisualBrush is not being rendered in the DrawRetangle(...) function. I can see the green border but nothing else. In addition, if I swap out the VisualBrush with a standard brush, it works great:
drawingContext.DrawRectangle(Brushes.Plum, new Pen(Brushes.Green, 2), new Rect(new Size(150, 150)));
Thanks in advance!

Take a look at this for an alternate way to create a BitmapSource from a UIElement:
MSDN Thread
I have also been trying to get the VisualBrush to work without any luck which brought me to this thread.
public static BitmapSource CreateBitmapSourceFromVisual(
Double width,
Double height,
Visual visualToRender,
Boolean undoTransformation)
{
if (visualToRender == null)
{
return null;
}
RenderTargetBitmap bmp = new RenderTargetBitmap((Int32)Math.Ceiling(width),
(Int32)Math.Ceiling(height), 96, 96, PixelFormats.Pbgra32);
if (undoTransformation)
{
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(visualToRender);
dc.DrawRectangle(vb, null, new Rect(new Point(), new Size(width, height)));
}
bmp.Render(dv);
}
else
{
bmp.Render(visualToRender);
}
return bmp;
}

I had the same issue before, I wanted to copy content of a UIElement to an image, I used the same approach in the answer above and it seems to work fine, only problem I has, I wanted it to work real-time, so I had to dig deeper, I found some references about using windows APIs to copy the content of the element into a Bitmap, and then this bitmap has to be converted into a BitmapSource so it's usable in WPF
so far it works fine, but it seems to leak memory (not sure about this). I will try to re-use the UIElement hwnd Handle and the bitmap object for better performance and the memory leak (if it exists)
[DllImport("gdi32.dll")]
private static extern bool BitBlt(
IntPtr hdcDest, // handle to destination DC
int nXDest, // x-coord of destination upper-left corner
int nYDest, // y-coord of destination upper-left corner
int nWidth, // width of destination rectangle
int nHeight, // height of destination rectangle
IntPtr hdcSrc, // handle to source DC
int nXSrc, // x-coordinate of source upper-left corner
int nYSrc, // y-coordinate of source upper-left corner
System.Int32 dwRop // raster operation code
);
[DllImport("User32.dll")]
public extern static System.IntPtr GetDC(System.IntPtr hWnd);
[DllImport("User32.dll")]
public extern static int ReleaseDC(System.IntPtr hWnd, System.IntPtr hDC); //modified to include hWnd
//[DllImport("gdi32.dll")]
//[return: MarshalAs(UnmanagedType.Bool)]
//internal static extern bool DeleteObject(IntPtr hObject);
private static Bitmap GetBitmapFromControl(Window element, int width, int height)
{
HwndSource hWnd = (HwndSource)HwndSource.FromVisual(element);
System.IntPtr srcDC = GetDC(hWnd.Handle);
Bitmap bm = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bm);
System.IntPtr bmDC = g.GetHdc();
BitBlt(bmDC, 0, 0, bm.Width, bm.Height, srcDC, 0, 0, 0x00CC0020 /*SRCCOPY*/);
ReleaseDC(hWnd.Handle, srcDC);
g.ReleaseHdc(bmDC);
g.Dispose();
return bm;
}
public static BitmapSource ToWpfBitmap(this Bitmap bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Bmp);
stream.Position = 0;
BitmapImage result = new BitmapImage();
result.BeginInit();
// According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."
// Force the bitmap to load right now so we can dispose the stream.
result.CacheOption = BitmapCacheOption.OnLoad;
result.StreamSource = stream;
result.EndInit();
result.Freeze();
//DeleteObject(bitmap.GetHbitmap());
bitmap.Dispose();
return result;
}
}

Related

PrintScreen and BitBlt causing screen flicker

I'm trying to take a screenshot of another window specified by a valid hwnd. The procedure works, however the window flickers when the screenshot is taken.
The first function uses PrintScreen, the second BitBlt. Each is called from a routine that takes a screenshot every 10 seconds.
Is there a way to avoid a flicker when using the PrintScreen or BitBlt functions?
Any help would be appreciated.
public Bitmap GetScreenshot(IntPtr ihandle)
{
IntPtr hwnd = ihandle;//handle here
RECT rc;
FocusWindow(ihandle);
GetWindowRect(hwnd, out rc);
Bitmap bmp = new Bitmap(rc.Right - rc.Left, rc.Bottom - rc.Top, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap;
try
{
hdcBitmap = gfxBmp.GetHdc();
}
catch
{
return null;
}
bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);//err here once
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
gfxBmp.Dispose();
return bmp;
}
public Bitmap CaptureWindowImage(IntPtr hWnd) //, System.Drawing.Rectangle wndRect)
{
IntPtr hWndDc = GetDC(hWnd);
IntPtr hMemDc = CreateCompatibleDC(hWndDc);
RECT rc;
GetWindowRect(hWnd, out rc);
IntPtr hBitmap = CreateCompatibleBitmap(hWndDc, rc.Width, rc.Height);
SelectObject(hMemDc, hBitmap);
BitBlt(hMemDc, 0, 0, rc.Width, rc.Height, hWndDc, 0, 0, TernaryRasterOperations.SRCCOPY);
Bitmap bitmap = Bitmap.FromHbitmap(hBitmap);
DeleteObject(hBitmap);
ReleaseDC(hWnd, hWndDc);
ReleaseDC(IntPtr.Zero, hMemDc);
DeleteDC(hMemDc);
return bitmap;
}

can't capture Full Screen image ?? (with taskbar and any other open windows)

i have a problem with this code :
i need to screenshot the full screen that i see with all things (taskbar, anything open).
my code is just giving me a cropped pic of just one window
Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height);
Graphics graphics = Graphics.FromImage(bitmap as Image);
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size);
bitmap.Save("D://Changes.jpg", ImageFormat.Jpeg);
Your display settings are set to 125% (or higher) zoom.
Your application isn't DPI aware. You can correct that by updating your application's manifest.
If that doesn't work for you (or you'd rather not use the manifest), you can pinvoke GetDeviceCaps API to get the correct width and height for CopyFromScreen.
Here are your native definitions:
private static class Win32Native
{
public const int DESKTOPVERTRES = 0x75;
public const int DESKTOPHORZRES = 0x76;
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hDC, int index);
}
And you'd call it as so:
int width, height;
using(var g = Graphics.FromHwnd(IntPtr.Zero))
{
var hDC = g.GetHdc();
width = Win32Native.GetDeviceCaps(hDC, Win32Native.DESKTOPHORZRES);
height = Win32Native.GetDeviceCaps(hDC, Win32Native.DESKTOPVERTRES);
g.ReleaseHdc(hDC);
}
using (var img = new Bitmap(width, height))
{
using (var g = Graphics.FromImage(img))
{
g.CopyFromScreen(0, 0, 0, 0, img.Size);
}
img.Save(#"C:\users\andy\desktop\test.jpg", ImageFormat.Jpeg);
}

Capture a image in specified region in a window

So my goal is to make my program that can take a picture in a rectangular region in a specific window.
Eg.) If I want to capture the black square (full) and only the square (not the any part of the window Y). How would I achieve this ?.
I have tried to use GDI32.dll but I can't seem to be able to configure it to capture only the black square.
public static Bitmap CaptureWindow(IntPtr handle) {
IntPtr hdcSrc = User32.GetWindowDC(handle);
User32.RECT windowRect = new User32.RECT {
left = 467,
right = 1432,
top = 381,
bottom = 994
};
int width = 965;
int height = 613;
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
GDI32.BitBlt(hdcDest, windowRect.right, windowRect.bottom, width, height, hdcSrc, windowRect.left, windowRect.top, GDI32.SRCCOPY);
GDI32.SelectObject(hdcDest, hOld);
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle, hdcSrc);
Image img = Image.FromHbitmap(hBitmap);
GDI32.DeleteObject(hBitmap);
return (Bitmap) img;
}

How do I take screenshot of a minimized Window?

I'm building a small video recording of a specific Windows but I just noticied it will not work when the Window is minimized, it take screenshot only the application's window superior bar. Is possible to workaround it?
Here's my current code:
public Bitmap GetScreenshot(IntPtr hwnd)
{
RECT rc;
if (!GetWindowRect(hwnd, out rc))
throw new Win32Exception(Marshal.GetLastWin32Error());
Bitmap bmp = new Bitmap(rc.right - rc.left, rc.bottom - rc.top, PixelFormat.Format32bppArgb);
using (var gfxBmp = Graphics.FromImage(bmp))
{
IntPtr hdcBitmap = gfxBmp.GetHdc();
bool succeeded = PrintWindow(hwnd, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!succeeded)
{
gfxBmp.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(Point.Empty, bmp.Size));
}
IntPtr hRgn = CreateRectRgn(0, 0, 0, 0);
GetWindowRgn(hwnd, hRgn);
Region region = Region.FromHrgn(hRgn);
if (!region.IsEmpty(gfxBmp))
{
gfxBmp.ExcludeClip(region);
gfxBmp.Clear(Color.Transparent);
}
return bmp;
}
}

Use Graphic.DrawLines(Pen pen, Points points) for Metafile, the picture cannot open when the count of points is large

I have a problem with a GDI+ metafile. I want to save a metafile by graphic. It works well when the point count is 10000 and the saved metafile can be opened. But when the point count is large (e.g. count = 10000000), the metafile cannot be opened by mspaint.exe.
Is there anything I missed? Is metafile record size limited? By the way, drawrectangles also has this issue.
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
int width = 1489;
int height = 471;
Graphics offScreenBufferGraphics;
Metafile m;
using (MemoryStream stream = new MemoryStream())
{
using (offScreenBufferGraphics = Graphics.FromHwndInternal(IntPtr.Zero))
{
IntPtr deviceContextHandle = offScreenBufferGraphics.GetHdc();
m = new Metafile(
stream,
deviceContextHandle,
new RectangleF(0, 0, width, height),
MetafileFrameUnit.Pixel,
EmfType.EmfPlusOnly);
offScreenBufferGraphics.ReleaseHdc();
}
}
using (Graphics g = Graphics.FromImage(m))
{
// Set everything to high quality
g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
MetafileHeader metafileHeader = m.GetMetafileHeader();
g.ScaleTransform(
metafileHeader.DpiX / g.DpiX,
metafileHeader.DpiY / g.DpiY);
g.PageUnit = GraphicsUnit.Pixel;
g.SetClip(new RectangleF(0, 0, width, height));
// clears the image and colors the entire background
g.Clear(Color.White);
// draw lines
using (Pen pen = new Pen(Color.Black, 1f))
{
Random rnd = new Random(DateTime.Now.Millisecond);
List<PointF> polyPoints = new List<PointF>();
const int count = 10000;
for (int i = 1; i <= 10000000; i++)
{
polyPoints.Add(new PointF(rnd.Next(1000),rnd.Next(1000)));
}
g.DrawLines(pen, polyPoints.ToArray());
// while
} // using
} // using
// Get a handle to the metafile
IntPtr iptrMetafileHandle = m.GetHenhmetafile();
// Export metafile to an image file
CopyEnhMetaFile(iptrMetafileHandle, #"F:\CacheToDisk\test2.emf");
// Delete the metafile from memory
DeleteEnhMetaFile(iptrMetafileHandle);
}
[DllImport("gdi32.dll")]
static extern IntPtr CopyEnhMetaFile( // Copy EMF to file
IntPtr hemfSrc, // Handle to EMF
String lpszFile // File
);
[DllImport("gdi32.dll")]
static extern int DeleteEnhMetaFile( // Delete EMF
IntPtr hemf // Handle to EMF
);
It seems like the limitation. If I use DrawPath instead of DrawLines, it works correctly.

Categories

Resources