Take screenshot from window content (without border) - c#

I am searching a solution on how to save the content of a form in a bitmap with C#.
I have already tried to use DrawToBitmap, but it captures all the window with the border.
That is the result of this code:
public static Bitmap TakeDialogScreenshot(Form window)
{
var b = new Bitmap(window.Bounds.X, window.Bounds.Y);
window.DrawToBitmap(b, window.Bounds);
return b;
}
Call is:
TakeDialogScreenshot(this);
Who thought it :D
I have already searched on google, but I did not manage to get it. Thanks!

Edit: While using the ClientArea is key, it isn't quite enough, as DrawToBitmap will always include the title, borders, scrollbars..
So after taking the full screen- or rather 'formshot', we'll have to crop it, using an offset we can get from mapping the origin of the clientarea to the screen coordinates and subtracting these from the form location, which already is in screen coordinates..:
public static Bitmap TakeDialogScreenshot(Form window)
{
var b = new Bitmap(window.Width, window.Height);
window.DrawToBitmap(b, new Rectangle(0, 0, window.Width, window.Height));
Point p = window.PointToScreen(Point.Empty);
Bitmap target = new Bitmap( window.ClientSize.Width, window.ClientSize.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(b, 0, 0,
new Rectangle(p.X - window.Location.X, p.Y - window.Location.Y,
target.Width, target.Height),
GraphicsUnit.Pixel);
}
b.Dispose();
return target;
}
Sorry for the error in my first post!

Related

How to imlement scrolling of the background in PictureBox programmatically?

I have a pictureBox in my program. This pictureBox is kind a "unlimited" working area for my other controls(it's width and height growing all the time, when some of my controls located inside this pictureBox came close to pictureBox's borders). I implement scrollBars for pictureBox by myself programatically, It's just changing control's location inside the pictureBox. Now i also want to implement scrollable background. I want that backgrounds position changed as well as a controls when i scroll scrollBar. I create code for that (i simplify it for easy reading. Here is only logic for horizontal scrollBar. I call it when ScrollBar event works):
public Bitmap DrawImageUnscaled(int x, int y, int width, int height)
{
//this is the part of the image that will be cropped
Bitmap croppedPartBitmpap = new Bitmap(width, height);
Rectangle croppedPartRectangle = new Rectangle(x, y, width, height);
using (var g = Graphics.FromImage(croppedPartBitmpap))
{
//BigBitmap - original background
g.DrawImage(this.BigBitmap, 0, 0, croppedPartRectangle, GraphicsUnit.Pixel);
}
//this is the part of the image that will left
Bitmap leftPartBitmap = new Bitmap(this.PanelWidth - width, height);
Rectangle leftPartRectangle = new Rectangle(width, y,this.PanelWidth - width, height);
using (var g = Graphics.FromImage(leftPartBitmap))
{
g.DrawImage(this.BigBitmap, 0, 0, leftPartRectangle, GraphicsUnit.Pixel);
}
//this is the merged image
Bitmap mergedBitmap = new Bitmap(this.PanelWidth, this.PanelHeight);
using (var g = Graphics.FromImage(mergedBitmap))
{
g.DrawImage(leftPartBitmap, 0, 0);
g.DrawImage(croppedPartBitmpap, leftPartBitmap.Width, 0);
}
return mergedBitmap;
}
When i scroll this pictureBox area, program is cropping part of background that goes out of the boundaries and after this merged it at end with the part that been left on the screen. It works okay, but it take so much memory, it can take 2gb and more. Can you help me to avoid of using such giant memoryt? Maybe the whole logic wrong and i should try better solution for that?

C# Graphics DrawImage function Re-scales bitmap extracted from an original bitmap

I have been trying to get the section of an image by giving the coordinates of the image. Here is the code that extracts image from the original image.
private byte[] ExtractImageSection(Bitmap originalMap, ImagePart imgPart)
{
Bitmap imageSection = new Bitmap(imgPart.Width, imgPart.Height);
int top = Math.Min(imgPart.Y1, imgPart.Y2);
int bottom = Math.Max(imgPart.Y1, imgPart.Y2);
int left = Math.Min(imgPart.X1, imgPart.X2);
int right = Math.Max(imgPart.X1, imgPart.X2);
Rectangle cloneRect = Rectangle.FromLTRB(left, top, right, bottom);
Graphics g = Graphics.FromImage(imageSection);
// Draw the given area (section) of the source image
// at location 0,0 on the empty bitmap (imageSection)
//g.DrawImage(originalMap, 0, 0, cloneRect, GraphicsUnit.Pixel);
g.DrawImage(originalMap, cloneRect, 0, 0, imgPart.Width, imgPart.Height, GraphicsUnit.Pixel);
/* Code Commented - Clone Bitmap method
int top = Math.Min(imgPart.Y1, imgPart.Y2);
int bottom = Math.Max(imgPart.Y1, imgPart.Y2);
int left = Math.Min(imgPart.X1, imgPart.X2);
int right = Math.Max(imgPart.X1, imgPart.X2);
Rectangle cloneRect = Rectangle.FromLTRB(left, top, right, bottom);
System.Drawing.Imaging.PixelFormat pixelFormat = originalMap.PixelFormat;
Bitmap imageSection = (Bitmap)originalMap.Clone(cloneRect, pixelFormat);*/
imageSection.Save(pageImageLocation + imgPart.ImagePartName, ImageFormat.Png);
byte[] imageSectionByte = ConvertBitmapIntoByte(imageSection);
return imageSectionByte;
}
The commented code represents the ways that I've used so far to achieve my goal. I used the following stack-overflow link to try to fix the issue but I still did not work. Here is the original image (originalMap)
Above function tries to get the highlighted section. The function gets X1, Y1, X2, Y2, Width, Heigth from front end. To get the required values I am using
imgAreaSelect
Things I have done so far to figure out the issue
I have also debugged the code to verify the coordinates, width, and
height of the extracted part. (they were correct)
Tried variants of DrawImage from Graphics class.
Tried Clone method of Bitmap to clone the original image based on the section coordinates as you can see the commented code.
Here is the image after the extract method.
Can anyone please help me figure out this workaround of the issue?

C# GDI+ ScaleTransform ok on picturebox but image saved is original

Hi I have the issue that when I use ScaleTransform(zoomFactor,zoomFactor) the image saved on disk is the original version always, while on screen in the picturebox the image is distorted in proportion to the zoomFactor.
Why this could be happening ? Shouldn't I have the final result as applied from e.Graphics on disk written image ?
My code is the following which is a version with matrix. but the instead of matrix I have used the ScaleTransform as well. Result is always the same:
g=e.Graphics;//inside picturebox_paint()
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
e.Graphics.DrawImage((Bitmap)bmp, 0, 0);
int seed = Convert.ToInt32(Regex.Match(Guid.NewGuid().ToString(), #"\d+").Value);
String destinationFile = #"C:\tmp\photoid\" + new Random(seed).Next() + "_conv.jpg";
//Here I get always the original image back!!!!
bmp.Save(destinationFile);
I have used as well the following idiom but with same results:
//Matrix matrix = new Matrix();
//matrix.Scale(zoomFac, zoomFac);
//e.Graphics.Transform = matrix;
You need to make the PictureBox draw the things it shows on screen into a new Bitmap, which you then can save!
As it is the Image will be saved in the original form and nothing you did in the Paint event, which actually painst onto the surface of the PictureBox will be saved.
So to save everything, i.e. The Image, possibly a BackgroundImage and all you draw in the Paint event you would call DrawToBitmap somehwere.
Somewhere means somewhere else, not in the Paint event, as it will call the Paint event to create the new Bitmap, causing an endless loop..
To call it you would do something like this:
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
pictureBox1.DrawToBitmap(bmpSave, pictureBox1.ClientRectangle);
But maybe this is not really what you want? Maybe you actually want to modify the Image? In that case do not use the Paint event at all!
Instead do something like this:
Bitmap bmpSave = new Bitmap(yourNewWidth, yourNewHeight);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.ScaleTransform(ratio * zoomFac, ratio * zoomFac);
g.DrawImage((Bitmap)pictureBox1.Image, 0, 0); //
pictureBox1.Image = bmpSave;
bmpSave.Save(...);
}
You could call this from somewhere where the scaling is being triggered from.
Note that doing the scaling repeatedly and each time from the previoulsy scaled version will degrade the quality rather fast. For this always scale from a saved version of the original!!
Btw: Using a Matrix for scaling doesn't really make a difference over ScaleTransform.
But if you want to do a direct scaling why not use the DrawImage overload which takes two Rectangles? This is the most common solution if all you want to to scale and maybe draw other stuff additionally..:
int newWidth = 100; int newHeight = 100; string yourFileName = "D:\\xyz123.jpg";
Bitmap bmpSave = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
Rectangle newRectangle = new Rectangle(0, 0, newWidth, newHeight);
Rectangle oldRectangle = new Rectangle(Point.Empty, pictureBox1.Image.Size);
using (Graphics g = Graphics.FromImage(bmpSave))
{
g.DrawImage((Bitmap)pictureBox1.Image, newRectangle, oldRectangle, GraphicsUnit.Pixel);
bmpSave.Save(yourFileName, ImageFormat.Jpeg);
}
And there there is the scaling Bitmap constructor:
Bitmap bmp = new Bitmap(pictureBox1.Image, newWidth, newHeight);
Which I would recommend if all you want is to scale the Image. As the other solutions it will not change the Image displayed until you assign it back into the PictureBox..:
pictureBox1.Image = bmp ;
Don't forget to dispose of the old Image..
Been a while since I messed with GDI but I think you need to copy back to the Bitmap here.
g.DrawImage(bmp, scaledwidth, scaledheight);
Try something like that before bmp.Save
Edit
Apologies for not seeing that you were copying back to the bitmap. Perhaps the overload which specifies the output rectangle is what you need. Try a DrawImage overload which has the destination Rect. https://msdn.microsoft.com/en-us/library/ms142040(v=vs.110).aspx

Bitmap Graphics vs WinForm Control Graphics

I'm just using a .NET port of Pdfium named PdfiumViewer. It just works very well once rendered in the WinForm controls but when I try to render it on a Bitmap to show in the WPF windows (or even saving to disk) the rendered text has problem.
var pdfDoc = PdfiumViewer.PdfDocument.Load(FileName);
int width = (int)(this.ActualWidth - 30) / 2;
int height = (int)this.ActualHeight - 30;
var bitmap = new System.Drawing.Bitmap(width, height);
var g = System.Drawing.Graphics.FromImage(bitmap);
g.FillRegion(System.Drawing.Brushes.White, new System.Drawing.Region(
new System.Drawing.RectangleF(0, 0, width, height)));
pdfDoc.Render(1, g, g.DpiX, g.DpiY, new System.Drawing.Rectangle(0, 0, width, height), false);
// Neither of these are readable
image.Source = BitmapHelper.ToBitmapSource(bitmap);
bitmap.Save("test.bmp");
// Directly rendering to a System.Windows.Forms.Panel control works well
var controlGraphics = panel.CreateGraphics();
pdfDoc.Render(1, controlGraphics, controlGraphics.DpiX, controlGraphics.DpiY,
new System.Drawing.Rectangle(0, 0, width, height), false);
It's notable to say that I tested almost every possible options on the Graphics object including TextContrast,TextRenderingHint,SmoothingMode,PixelOffsetMode, ...
Which configurations I'm missing on the Bitmap object that cause this?
Edit 2
After lots of searching and as #BoeseB mentioned I just found that Pdfium render device handle and bitmaps differently by providing a second render method FPDF_RenderPageBitmap and currently I'm struggling to convert its native BGRA bitmap format to managed Bitmap.
Edit
Different modes of TextRenderingHint
Also tried Application.SetCompatibleTextRenderingDefault(false) with no noticeable difference.
Isn't it your issue ?
Look recent fix for it.
As you can see, repository owner commited newer version of PdfiumViewer. Now you can write this way:
var pdfDoc = PdfDocument.Load(#"mydoc.pdf");
var pageImage = pdfDoc.Render(pageNum, width, height, dpiX, dpiY, isForPrinting);
pageImage.Save("test.png", ImageFormat.Png);
// to display it on WPF canvas
BitmapSource source = ImageToBitmapSource(pageImage);
canvas.DrawImage(source, rect); // canvas is instance of DrawingContext
Here is a popular approach to convert Image to ImageSource
BitmapSource ImageToBitmapSource(System.Drawing.Image image)
{
using(MemoryStream memory = new MemoryStream())
{
image.Save(memory, ImageFormat.Bmp);
memory.Position = 0;
var source = new BitmapImage();
source.BeginInit();
source.StreamSource = memory;
source.CacheOption = BitmapCacheOption.OnLoad;
source.EndInit();
return source;
}
}

Png image processing in .NET

I have the following task. Take a base image and overlay on it another one. The base image is 8b png as well as overlay.
Here are the base (left) and overlay (right) images.
Here is a result and how it must look.
The picture in the left is a screenshot when one picture is on top of another (html and positioning) and the second is the result of programmatic merging.
As you can see in the screenshot the borders of the text is darker. Also here are the sizes of the images
Base image 14.9 KB
Overlay image 6.87 KB
Result image 34.8 KB
The size of the resulting image is also huge
Here is my code that I use to merge those pictures
/*...*/
public Stream Concatinate(Stream baseStream, params Stream[] overlayStreams) {
var #base = Image.FromStream(baseStream);
var canvas = new Bitmap(#base.Width, #base.Height);
using (var g = canvas.ToGraphics()) {
g.DrawImage(#base, 0, 0);
foreach (var item in overlayStreams) {
using (var overlayImage = Image.FromStream(item)) {
try {
Overlay(#base as Bitmap, overlayImage as Bitmap, g);
} catch {
}
}
}
}
var ms = new MemoryStream();
canvas.Save(ms, ImageFormat.Png);
canvas.Dispose();
#base.Dispose();
return ms;
}
/*...*/
/*Tograpics extension*/
public static Graphics ToGraphics(this Image image,
CompositingQuality compositingQuality = CompositingQuality.HighQuality,
SmoothingMode smoothingMode = SmoothingMode.HighQuality,
InterpolationMode interpolationMode = InterpolationMode.HighQualityBicubic) {
var g = Graphics.FromImage(image);
g.CompositingQuality = compositingQuality;
g.SmoothingMode = smoothingMode;
g.InterpolationMode = interpolationMode;
return g;
}
private void Overlay(Bitmap source, Bitmap overlay, Graphics g) {
if (source.Width != overlay.Width || source.Height != overlay.Height)
throw new Exception("Source and overlay dimensions do not match");
var area = new Rectangle(0, 0, source.Width, source.Height);
g.DrawImage(overlay, area, area, GraphicsUnit.Pixel);
}
My questions are
What should I do in order to merge images to achieve the result as in the screenshot?
How can I lower the size of the result image?
Is the System.Drawing a suitable tool for this or is there any better tool for working with png for .NET?
The answers to your questions are:
1) Just call method ToGraphics with a argument CompositingQuality.Default instead of using default argument values like in the example:
using (var g = canvas.ToGraphics(compositingQuality: CompositingQuality.Default))
The problem is with CompositingQuality.HighQuality is that it makes a composition of both images into one, but you want to make an overlay, not to make a composition of two images.
2) The size will be similar to the one you specified and that can not be changed, it is due to a image format.
3) If you are programming in c# for desktop, than System.Drawing is the best choice as far as I know.

Categories