How to properly cope with Memory residing Bitmap resize - c#

I have bitmap residing in memory (coming from my webcam but I don't think that this makes a difference.
It is 960x540 120dpi
you see that the picture in the lower part gets till the point where my shirt begins.
I know the bmp dimensions since I put this code prior resize
using (var fileStream = new FileStream(#"C:\temp\3.bmp", FileMode.Create))
{
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(source));
encoder.Save(fileStream);
}
and the result is the picture above.
Then I resize it with that:
var resizedImage = new RenderTargetBitmap(
width, height, // Resized dimensions 200x112
source.DpiX, source.DpiY,// Default DPI values
PixelFormats.Default); // Default pixel format
and the result is the bmp below which is properly 200x112 but it cuts out in part of the image in the lower and right part.
I have seen that the problem is related with the dpi value in the RenderTargetBitmap instruction. If I divide the dpi by 1.25 everything gets fine but why 1.25???????
Thank you in advance for any help
Patrick
--ADD--
There is something additional that I can't understand: I know the initial bitmap size for I have saved it to filesytem with the instructions above.
But if I look at the properties by putting a breakpoint I see:
BITMAP BEFORE RESIZE
width,height = 768, 432
pixelwidth, pixelheight = 960, 540
dpiX, dpiY= 120, 120
BITMAP AFTER RESIZE
width,height = 160, 89
pixelwidth, pixelheight = 200, 112
dpiX, dpiY= 120, 120
now I know that what counts here is pixelwidth, pixelheight so that's correct.
If I do 960/786 I get 1.25! So that's my number but why? Can I correct the code as to make it a general solution???

Instead of a RenderTargetBitmap you should simply use a TransformedBitmap with an appropriate ScaleTransform:
var scale = 200d / 960d;
var resizedImage = new TransformedBitmap(source, new ScaleTransform(scale, scale));

Related

Graphics is blank when trying to turn anti-aliasing off for a bitmap (C#)

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;

Position and size of combined images inside a new Bitmap

I'm trying to combine two semi-transparent PNG images and display the result in a pictureBox1 which has its SizeMode property set to Zoom:
pictureBox1.Image = Image.FromFile(imgPath + "/myImg1.png");
If I directly display a single image, it is in the center and the existing borders are respected (as shown on the right side of example image below), but if I combines these two images - of the same size:
private void button2_Click(object sender, EventArgs e)
{
source1 = (Bitmap)Image.FromFile(imgPath + "/myImg1.png");
source2 = (Bitmap)Image.FromFile(imgPath + "/myImg2.png");
var target = new Bitmap(source1.Width, source1.Height, PixelFormat.Format32bppArgb);
var graphics = Graphics.FromImage(target);
graphics.CompositingMode = CompositingMode.SourceOver;
graphics.DrawImage(source1, 0, 0);
graphics.DrawImage(source2, 0, 0);
pictureBox1.Image = target;
}
the actual result shows the two images cropped and not centered (as shown on left side):
I'm trying to figure out how control the position and size of the two combined images, so they're drawn as shown in the image on the right.
The problem:
The OP is trying to center two images, of the same size in this case, inside a new Bitmap container. The size of the destination Bitmap is set to the size of one of the source images. The new Bitmap should be presented in a PictureBox. The control's SizeMode property is set to SizeMode.Zoom.
The unexpected result is shown in the image on the left, the expected result on the right:
What happens:
The destination Bitmap is sized as one of the source images, both of the same size.
The two source images are then drawn an Point(0, 0) in the new container.
It's expected - since the two semi-transparent images have the same size - that both will be drawn in the original position. The source images instead seem to be enlarged and moved towards the bottom-right corner of the new container.
It doesn't seem to be, this is exactly what happens.
The two source Images have a DPI descriptor set to ~72 DPI.
A standard PC screen has a resolution of at least 96 DPI.
When a new Bitmap is created as:
var newImage = new Bitmap(source1.Width, source1.Height, PixelFormat.Format32bppArgb);
the resolution of the new Bitmap is set to the screen resolution, hence at least 96 DPI.
An image loaded using this method:
var image = Image.FromFile([Image Path]);
is created using the original DPI descriptor and pixel format.
When a 72 DPI image is draw in a 96 DPI container, the different resolution is taken into consideration: the lower resolution image is enlarged.
As a result, the two 72 DPI images, draw inside a 96 DPI container of the same size, are not centered anymore, but it appears they're moved down and to the right (thus also clipped).
See also: Image is not drawn at the correct spot
How to solve:
When processing images of - quite possibly - different resolutions, pixel format and size, it's preferable to create a copy of the source images and work with new containers that have the same definition.
So, we can copy the source images to a standard 32Bit ARGB Bitmap (default format, hardware-aligned), setting the resolution to the current screen resolution or specifying a different resolution for specific uses (for printing, for example).
Or a value that a User can specify.
As mentioned, when a new Bitmap container is created as:
var bitmap = new Bitmap([width], [height]);
the .Net implementation generates a Bitmap with PixelFormat.Format32bppArgb and resolution set to what the Application thinks is the screen DPI. The value returned by Control.DeviceDpi.
A non DpiAware application will most often think it's 96 DPI.
We can nonetheless specify the image resolution using the Bitmap.SetResolution() method (float values, the DPI is always expressed in floating point values).
This Bitmap constructor:
var bitmap = new Bitmap([Stream], true, false))
generates a Format32bppArgb Bitmap, loads the ICM settings (Color Definition) - if any - and skips the validation of the original Bitmap (often used when a lot of image files are read from disk, it's quite faster. Skipping the ICM mapping is also faster, but can produce wrong colors).
We should also suppose that the two images to center inside the new container, may not have the same exact size (or not the same size at all).
Hence we need a new Bitmap that can contain both images, evaluating the maximum dimension of both.
All considered, the original code can be changed as described in the CenterImages() method.
The PictureBox Image can be then set as:
string image1Path = Path.Combine(imgPath, "myImg1.png");
string image2Path = Path.Combine(imgPath, "myImg2.png");
pictureBox1.Image?.Dispose();
pictureBox1.Image = CenterImages(image1Path, image2Path);
to generate a new combined Bitmap using a default 96 DPI resolution. Or as:
pictureBox1.Image?.Dispose();
pictureBox1.Image = CenterImages(image1Path, image2Path, 300.0f);
to generate a new Bitmap with a resolution of 300 DPI.
private Bitmap CenterImages(string sourcePath1, string sourcePath2, float dpi = 96.0f)
{
using (var image1 = new Bitmap(Image.FromStream(
new MemoryStream(File.ReadAllBytes(sourcePath1)), true, false)))
using (var image2 = new Bitmap(Image.FromStream(
new MemoryStream(File.ReadAllBytes(sourcePath2)), true, false))) {
image1.SetResolution(dpi, dpi);
image2.SetResolution(dpi, dpi);
var rect = new Rectangle(0, 0,
Math.Max(image1.Width, image2.Width), Math.Max(image1.Height, image2.Height));
var combinedImage = new Bitmap(rect.Width, rect.Height);
combinedImage.SetResolution(dpi, dpi);
using (var graphics = Graphics.FromImage(combinedImage)) {
graphics.DrawImage(image1, (rect.Width - image1.Width) / 2, (rect.Height - image1.Height) / 2);
graphics.DrawImage(image2, (rect.Width - image2.Width) / 2, (rect.Height - image2.Height) / 2);
}
return combinedImage;
}
}

Image is not drawn at the correct spot

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;
}

Replicating WinRT Pixel Density changes in WPF possibly using DPI

I am using WPF to generate tile images for my WinRT app. I have a tile UserControl which is converted to PNG and this works well (It does, please don't tell me otherwise). WinRT passes me a scale property 100% (320x150), 140% (434x210) and 180% (558x270) which I use to generate the correct size of image.
For when I want to use images in my tile. I have replicated the image selection functionality of WinRT (You can supply thee scales of images and WinRT apps automatically select the correct scale) in my tile UserControl's code behind. So depending on the scale I select a bigger or smaller image source. However, on the larger scales my font size remains the same size and looks really small. I don't think I need to change the font size based on the scale as this is not what happens in WinRT, so it must be the code I'm using to convert my UserControl to a PNG and something to do with DPI. Here is my conversion Code:
// I pass in 320x150 or 434x210 or 558x270 depending on the scale.
public static MemoryStream ToPng(
this FrameworkElement frameworkElement,
double width,
double height)
{
BitmapSource bitmapSource = ToBitmapSource(frameworkElement, width, height);
PngBitmapEncoder pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmapSource));
MemoryStream memoryStream = new MemoryStream();
pngBitmapEncoder.Save(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
public static BitmapSource ToBitmapSource(
this FrameworkElement frameworkElement,
double width,
double height)
{
Size renderingSize = new Size(width, height);
frameworkElement.Measure(renderingSize);
Rect renderingRectangle = new Rect(new Point(0, 0), renderingSize);
frameworkElement.Arrange(renderingRectangle);
frameworkElement.UpdateLayout();
Rect bounds = VisualTreeHelper.GetDescendantBounds(frameworkElement);
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)frameworkElement.ActualWidth,
(int)frameworkElement.ActualHeight,
96,
96,
PixelFormats.Pbgra32);
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
VisualBrush visualBrush = new VisualBrush(frameworkElement);
drawingContext.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
}
renderBitmap.Render(drawingVisual);
return renderBitmap;
}
Thanks for any help. Much appreciated.
I needed to change the above code so that I measure and arrange the frameworkElement as 310x150. I then render it to the final scale size e.g. 558x270 and set the DPI to (96/100)*scale where the scale is 180 in this case.

How draw rectangle in millimeter

using (var mem = new MemoryStream())
using (var bmp = new Bitmap(85, 54))
using (var gfx = Graphics.FromImage((Image)bmp))
{
// gfx.SmoothingMode = SmoothingMode.AntiAlias;
gfx.PageUnit = GraphicsUnit.Millimeter;
gfx.FillRectangle(Brushes.Red, new Rectangle(0, 0, bmp.Width, bmp.Height));
//add question
gfx.DrawString(captcha, new Font("Arial", 5), Brushes.Blue, bmp.Width / 2, bmp.Height/2);
//render as Jpeg
bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg);
img = this.File(mem.GetBuffer(), "image/Jpeg");
}
return img;
this not work.
I need 85x54 millimeter
how do this?
I need draw for print
The size of this Bitmap is in pixels.
When you display a bitmap on a regular display a single pixel will be 1/96th of an inch. Other displays might have other DPI's (Dots Per Inch) - such as Retina displays
Most printers support at least 300 DPI.
So what you need to do is get the DPI of the screen or printer and size the bitmap accordingly or use a image format (vector?) that allows you to specify the DPI. Some bitmap formats also allow you to specify the intended DPI
Digital images are always in pixels. Never in millimeters or inches. Depending on the DPI (dots per inch) you'll use when printing, the pixels are translated to millimeters or inches.
For screen, use 72 pixels per inch, for print use 300.
For your picture (85x54mm = 3.34x2.12in) use (3.34 * 300) x (2.12 * 300) = 1002 x 637 pixels for print.

Categories