I am trying to capture a part of my screen.
The problem is even if I use png to save the image the quality still worse than if I would just use normal print screen.
Here is the code I use:
//display a save file dialog for the user to set the file name
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "PNG (*.png)|*.png";
saveFileDialog.FilterIndex = 0;
saveFileDialog.RestoreDirectory = true;
//if the user proceed saving the picture
if (saveFileDialog.ShowDialog() == DialogResult.OK)
{
//simplify code with constant numbers for demo
//get the width of the panel we need the screenshoot off
int x = 10;
//get the height of the panel we need the screenshoot off
int y = 10;
//create the ractangle of the screenshoot panel
Rectangle rect = new Rectangle(x, y, 5, 5);
//create new bitmap
Bitmap bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bmp);
//get the screenshoot of the panel
g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
string fileName = saveFileDialog.FileName;
if (!fileName.Contains(".png"))
fileName += ".png";
bmp.Save(fileName, ImageFormat.Png);
}
EDIT:
Example image form what I take with code:
Normal screenshot:
It does not look so different here, but it is worst.
The top image in your question was rescaled, smaller than the original. This is noticeable in images that contain fine detail, like the ClearType anti-aliasing pixels used to make the text more readable. When they get rescaled, the visual effect is ruined and text looks a lot worse.
It is entirely unclear why the image was rescaled, nothing in your code could cause that. Double-check by using the debugger to inspect the bmp.HorizontalResolution property, it should match the DPI of your video adapter. Simplest explanation that it was done by whatever image viewing program you used, perhaps to make the image fit the window. Try zooming out.
If it's possible to use a external library I suggest you FMUtils.Screenshot. Its available as a NuGet-package.
I just tried it and the quality is like the standard screenshot from windows. Here's a shortexample:
new ComposedScreenshot(new Rectangle(0, 0, 100, 100)).ComposedScreenshotImage.Save(#"PATH_TO_FILE\example-screenshot.png", ImageFormat.Png);
Hope this helps!
The pixel format you use only uses 8 bits each for the different color channels. You could try using PixelFormat64bppARGB to get 16 bits per color.
Resource on the PixelFormat enumeration: http://msdn.microsoft.com/en-us/library/system.drawing.imaging.pixelformat%28v=vs.110%29.aspx
Related
What I'm trying to do:
Since in my bitmaps there are some unwanted white edges around the picture that result from anti-aliasing as pointed out from another user from stackoverflow.
I'm trying to convert an image that's inputted into a bitmap, convert bitmap into a Graphics object so that I can set the Smooth Mode to none, and then finally convert that Graphics object to a bitmap so that it can be copied by the user after setting it to the clipboard. I'm not sure if this is a good way of getting rid anti-aliasing in bitmaps but I'm definitely interested in improvements and suggestions.
The issue I'm facing:
The result of the image after is completely blank and does not contain any of the pixels that are previously found in the original bitmap. Here's the result:
This issue applies to all pictures no matter what their format is.
My code:
public PicGen(PictureBox pictureBox)
{
Clipboard.Clear();
Bitmap firstImage = new(pictureBox.Image, pictureBox.Width, pictureBox.Height);
RectangleF cloneRect = new RectangleF(0, 0, firstImage.Width, firstImage.Height);
System.Drawing.Imaging.PixelFormat format = firstImage.PixelFormat;
Bitmap cloneBitmap = firstImage.Clone(cloneRect, format);
Graphics AntiARemover = Graphics.FromImage(cloneBitmap);
AntiARemover.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
Bitmap finalImg = new(52, 52, AntiARemover);
Clipboard.SetImage(finalImg);
Color backColorBottom = firstImage.GetPixel(0, 0);
firstImage.ReplaceColor(backColorBottom, Color.FromArgb(54, 57, 63));
Bitmap finalImg = new(52, 52, AntiARemover);
From the documentation for this bitmap constructor:
The new Bitmap that this method creates takes its horizontal and vertical resolution from the DpiX and DpiY properties of g, respectively.
If you want create a new image with the content from another you need to call one of the DrawImage methods. You should also dispose your graphics object, and any temporary bitmaps you may use.
using var finalImg = new Bitmap(52,52);
using var graphics = Graphics.FromImage(finalImg);
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
graphics.DrawImage(cloneBitmap)
However, edge artifacts typically occur when combining two images using an alpha channel, see Premultiplied alpha. In your example I can only see one input image, so I'm really not sure what it is you are actually trying to do. If you need to convert to premultiplied alpha you can use the following code to convert the color for each pixel
premultiplied.R = (byte)(straight.R * straight.A / 255);
premultiplied.G = (byte)(straight.G * straight.A / 255);
premultiplied.B = (byte)(straight.B * straight.A / 255);
premultiplied.A = straight.A;
Bitmap image = ReadBitmap("image.png");
Bitmap imageCopy = new Bitmap(image);
Bitmap canvas = new Bitmap(imageCopy.Width+100, imageCopy.Height);
// From this bitmap, the graphics can be obtained, because it has the right PixelFormat
using(Graphics g = Graphics.FromImage(canvas))
{
// Draw the original bitmap onto the graphics of the new bitmap
g.DrawImage(image, 0, 0);
}
// Use tempBitmap as you would have used originalBmp
InputPictureBox.Image = image;
OutputPictureBox.Image = canvas;
I haven't understood the output of this c# code.
The original image is not placed at the correct position. It should have been at (0, 0).
Also, I need a black background.
So, what is going on and how to correct this?
You are loading an Image, then a copy of this source is created using:
Bitmap bitmap = new Bitmap();
When you create a copy of an Image this way, you sacrifice/alter some details:
Dpi Resolution: if not otherwise specified, the resolution is set to the UI resolution. 96 Dpi, as a standard; it might be different with different screen resolutions and scaling. The System in use also affects this value (Windows 7 and Windows 10 will probably/possibly provide different values)
PixelFormat: If not directly copied from the Image source or explicitly specified, the PixelFormat is set to PixelFormat.Format32bppArgb.
From what you were saying, you probably wanted something like this:
var imageSource = Image.FromStream(new MemoryStream(File.ReadAllBytes(#"[SomeImageOfLena]"))), true, false)
var imageCopy = new Bitmap(imageSource.Width + 100, imageSource.Height, imageSource.PixelFormat))
imageCopy.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution);
using (var g = Graphics.FromImage(imageCopy)) {
g.Clear(Color.Black);
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(imageSource, (imageCopy.Width - imageSource.Width) / 2, 0);
pictureBox1.Image?.Dispose();
pictureBox2.Image?.Dispose();
pictureBox1.Image = imageSource;
pictureBox2.Image = imageCopy;
}
This is the result:
(The upper/lower frame black color is actually the Picturebox background color)
When the original Image Dpi Resolution is different from the base Dpi Resolution used when creating an Image copy with new Bitmap(), your results may be different from what is expected.
This is what happens with a source Image of 150, 96 and 72 Dpi in the same scenario:
Another important detail is the IDisposable nature of the Image object.
When you create one, you have to Dispose() of it; explicitly, calling the Dispose method, or implicitly, enclosing the Image contructor in a Using statement.
Also, possibly, don't assign an Image object directly loaded from a FileStream.
GDI+ will lock the file, and you will not be able to copy, move or delete it.
With the file, all resources tied to the Images will also be locked.
Make a copy with new Bitmap() (if you don't care of the above mentioned details), or with Image.Clone(), which will preserve the Image Dpi Resolution and PixelFormat.
I am not completely clear on what you are actually needing to do. But anyway, here is a WPF-friendly example of how to draw an image at a specific position inside another image.
Note if all you want to do is display the image in different size and/or put a black border around it, there are much simpler ways to do simply that, without having to create a second image, such as just laying out the image inside a panel that already has the border style you want.
Notice that I am using classes from the System.Windows.Media namespace because that is what WPF uses. These don't mix easily with the older classes from System.Drawing namespace (some of the class names conflict, and Microsoft's .Net framework lacks built-in methods for converting objects between those types), so normally one needs to simply decide whether to use one or the other sets of drawing tools. I assume you have been trying to use System.Drawing. Each has its own pros and cons that would take too long to explain here.
// using System.Windows.Media;
// using System.Windows.Media.Imaging;
private void DrawTwoImages()
{
// For InputPictureBox
var file = new Uri("C:\\image.png");
var inputImage = new BitmapImage(file);
// If your image is stored in a Resource Dictionary, instead use:
// var inputImage = (BitmapImage) Resources["image.png"];
InputPicture.Source = inputImage;
// imageCopy isn't actually needed for this example.
// But since you had it in yours, here is how it's done, anyway.
var imageCopy = inputImage.Clone();
// Parameters for setting up our output picture
int leftMargin = 50;
int topMargin = 5;
int rightMargin = 50;
int bottomMargin = 5;
int width = inputImage.PixelWidth + leftMargin + rightMargin;
int height = inputImage.PixelHeight + topMargin + bottomMargin;
var backgroundColor = Brushes.Black;
var borderColor = (Pen) null;
// Use a DrawingVisual and DrawingContext for drawing
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
// Draw the black background
dc.DrawRectangle(backgroundColor, borderColor, new Rect(0, 0, width, height));
// Copy input image onto output image at desired position
dc.DrawImage(inputImage, new Rect(leftMargin, topMargin,
inputImage.PixelWidth, inputImage.PixelHeight));
}
// For displaying output image
var rtb = new RenderTargetBitmap( width, height, 96, 96, PixelFormats.Pbgra32 );
rtb.Render(dv);
OutputPicture.Source = rtb;
}
i have colde like this :
Bitmap bmp = new Bitmap(width, height);
I just take a capture of window. Now i want just resize this captured bitmap (bmp).
How can i cut my bmp for example
RECT rt = new RECT();
GetWindowRect(hwnd1, out rt);
Int32 width = rt.Right - rt.Left;
Int32 height = rt.Bottom - rt.Top;
int leftttt = rt.Left + (width - 202);
int width2 = rt.Right - leftttt;
// // I want cut like this :
//
// in x=lefttt y = rt.Top Size ( width2,height)
And later i can easy save file to check my results by: (but won't do that only for check)
bmp.Save(#"D:\test.jpg", ImageFormat.Jpeg);
EDIT: I Want just cut not resize .
When i do code :
var graph = Graphics.FromImage(scren_kurwa.Image);
graph.DrawImage(bmp.Image, 10, 10, 200, 200);
And i save it its just override my bmp screen and just take a capture just in smaller version.
I just want to cut for examaple i want show only 1/4 of width this screen and save it to file. ( just save 1/4 width not more).
EDIT 2 :
graph.CopyFromScreen(leftttt, rt.Top, 0, 0, new Size(width2, height), CopyPixelOperation.SourceCopy);
This code above just doing what i want but i don't want again copy from screen i want copy this from bmp captured before.
Please be patient for newbies . I searched forums and just can't find solution.
Thank you.
EDIT 3
I just did how you wrote :
Rectangle cropRect = new Rectangle(100,100,100,100);
Bitmap bmp1 = new Bitmap(bmp1.Image);
bmp1.Clone(cropRect, bmp.PixelFormat);
bmp1.Save(#"D:\xdddde.jpg", ImageFormat.Jpeg);
But it don't cut an image just display the same as i had bmp.
This should work for you:
Bitmap cuttedImage;
using(Bitmap originalImage = new Bitmap("filePathName"))
{
Rectangle cropRect = new Rectangle(...);
cuttedImage = originalImage .Clone(cropRect, originalBmp.PixelFormat);
}
cuttedImage.Save("filePathName", ImageFormat.Jpeg);
cuttedImage.Dispose();
Note that this will create a shallow copy of your Bitmap. In your case that does not seem to be a problem, but keep that in mind.
Also make sure to check the MSDN documentation for exception handling. Either check that the rectangle is bigger than 0 and not bigger than the original image beforehand or catch the exceptions.
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
I'm able to save the captured image from a barcode scanner using this code:
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.DefaultExt = ".jpg";
dlg.Filter = "JPEG Images (.jpg)|*.jpg|All files (*.*)|*.*";
if (dlg.ShowDialog() == true)
{
using (FileStream file = File.OpenWrite(dlg.FileName))
{
file.Write(e.ImageBuffer, 0, e.ImageSize);
}
}
However, I would like to display the captured image using WPF but I get a distorted image.
private void _barcodeScannerInstance_SavePhotoEvent(object sender, ImageEventArgs e)
{
SetBitmap(e.ImageBuffer, 350, 263, 96);
}
private void SetBitmap(byte[] image, int width, int height, int dpi)
{
MainWindow.Instance.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
BitmapSource bitmapSource = BitmapSource.Create(
width, height, (double)dpi, (double)dpi, PixelFormats.Bgr24, null, image, ((width * 24 + 31) & ~31) >> 3);
HwModeScreen.BarcodeImageCanvas.Children.Clear();
Image myImage = new Image();
myImage.Width = HwModeScreen.BarcodeImageCanvas.ActualWidth;
myImage.Height = HwModeScreen.BarcodeImageCanvas.ActualHeight;
myImage.Stretch = Stretch.Fill;
myImage.Source = bitmapSource;
HwModeScreen.BarcodeImageCanvas.Children.Add(myImage);
});
Here is the image I see. It should be a black and white picture of a kleenex box.
Here is the saved jpg file:
did you mix up width and height? are you sure your dpi value is correct?
I suspect the whole problem is this line:
BitmapSource bitmapSource = BitmapSource.Create(
width, height, (double)dpi, (double)dpi, PixelFormats.Bgr24, null, image, ((width * 24 + 31) & ~31) >> 3)
What I would do to debug the issue is to write out the image to file and confirm all the inputs. Use photoshop, paint.net, file properties...
Are you sure you are working with bitmap format?
Are you sure you are working with 24bits per pixel?
Are you sure you have height and width correct, and you are feeding the values into the correct argument
What is this line all about, and why are you doing it? I am slightly suspicious.
((width * 24 + 31) & ~31) >> 3)
Basically, the way I look at this is that you are feeding the bitmap library a stream of bits... it doesn't know what the bits are but it will attempt to create the image from the information you give it: bits per pixel, size, etc. If you give it incorrect information, it will create a corrupted image as you have shown.
I am slightly suspicious that the problem is not with width and height; even if you mix those two values up-- I think you would get at least part of the first row of pixels to be rendered correctly. I see static / noise / snow, which tells me that there is something about the way the stream of bits was interpreted-- it is rendered as random blacks and whites.
Another thing: in your screen cap, I see color. this is another hint that there is something incorrect about your assumptions about the image. The values should probably 1 to 256 ( 8 bits per pixel I think? ) I would try creating a 8 bit per pixel black and white bitmap. Somehow the library thinks this is a color image.
I just noticed that you are assuming jpeg. jpeg is a lossy format-- I would have assumed that you would end up with a bitmap or tiff image. double check that you are indeed getting back a jpeg image (check the barcode api documentation)
The JPEG compression algorithm is quite unsuitable for the kind of image you are capturing. It works well for photos, it behaves poorly on images containing fine lines. The slight artifacts the compression produces makes it a lot harder to properly scan the barcode.
You don't see the Kleenex box because you are writing the raw image bytes. You need to use an image encoder. I recommend you use the PngBitmapEncoder class. GifBitmapEncoder should work too since you don't need a lot of colors, it makes smaller files. A code snippet that shows how to use an encoder is available here.
this is likely distorting it
myImage.Stretch = Stretch.Fill;
I used a jpeg decoder to fix the problem.
private void SetBitmap(byte[] image, int width, int height, int dpi)
{
MainWindow.Instance.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
BMemoryStream ms = new MemoryStream(image);
JpegBitmapDecoder decoder = new JpegBitmapDecoder(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapSource bitmapSource = decoder.Frames[0];
HwModeScreen.BarcodeImageCanvas.Children.Clear();
Image myImage = new Image();
myImage.Width = HwModeScreen.BarcodeImageCanvas.ActualWidth;
myImage.Height = HwModeScreen.BarcodeImageCanvas.ActualHeight;
myImage.Stretch = Stretch.Fill;
myImage.Source = bitmapSource;
HwModeScreen.BarcodeImageCanvas.Children.Add(myImage);
});