Change image size (width, height) to have defined dpi - c#

i try to explain what i need.
1 -My goal condition is to transform black pixels of image in Cartesian points without resize image. (ok, Done)
2 -Second goal condition is to resize image and redo step 1. (ok, i have normalized points with original image size and the job is done when changed width or height!)
3 - Now i need to reduced number of pixels in image to have defined number of DPI in my image to redo step 1. How? I have found the method setResolution(..,..) but how i must change the width and height of my image to obtain the correct resolution in terms of DPI? (see [?????] in code )
var image2 = new Bitmap(canvasWidth, canvasHeight);
image2.SetResolution(200.0f,200.0f); // I need this for example!
using (System.Drawing.Graphics gr = System.Drawing.Graphics.FromImage(image2)) {
gr.SmoothingMode = SmoothingMode.HighSpeed;
gr.InterpolationMode = InterpolationMode.Low;
gr.PixelOffsetMode = PixelOffsetMode.None;
gr.Clear(Color.White);
gr.DrawImage(this.LoadedImage, new System.Drawing.RectangleF(new PointF((float)this.Centre.x, (float)this.Centre.y), new Size(canvasWidth [?????], canvasHeight[??????])));
return image2;
}
If i run over everyone pixel in my new image2 i have the same result like run over OriginalImage. Well,in the end i need to reduced the number of pixels of my image to obtain a defined Dots Per Inch result.
I hope I was clear.
Thanks.

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

Fillholes function in Aforge

I need to use Fillholes function of Aforge, it accepts binary image. I manipulated all pixels to black or white pixels using following code in c#:
bitmapimage.SetPixel(i, j, Color.FromArgb(255,255,255)); // for white pixel
bitmapimage.SetPixel(i, j, Color.FromArgb(0,0,0)); // for black pixel
But when I apply fillholes function to bitmap image, I get this exception:
"Source pixel format is not supported by the filter"
Kindly anyone help why I am getting this exception ... is bitmap image not converted to Binary by all using setpixel?
Just changing the pixel colors will not change the pixel format of your image.
You first need to make sure that you have a gray scale image using some gray scale filter, then make sure that the gray scale image is binary through some threshold filter. Once the image has been pre-processed using these steps, you may apply the FillHoles filter.
AForge.NET offers helper classes to merge several filters, so you can combine all three filters into one total filter using the FiltersSequence class.
Assuming that your original Bitmap image is named bitmap, you can then apply the fill holes filter for example like this:
var filter = new FiltersSequence(Grayscale.CommonAlgorithms.BT709,
new Threshold(100), new FillHoles());
var newBitmap = filter.Apply(bitmap);
AForge FillHoles Class
The filter allows to fill black holes in white object in a binary image. It is possible to specify maximum holes' size to fill using MaxHoleWidth and MaxHoleHeight properties.
The filter accepts binary image only, which are represented as 8 bpp images.
Sample usage:
C#
// create and configure the filter
FillHoles filter = new FillHoles( );
filter.MaxHoleHeight = 20;
filter.MaxHoleWidth = 20;
filter.CoupledSizeFiltering = false;
// apply the filter
Bitmap result = filter.Apply( image );
The above was found at http://www.aforgenet.com/framework/docs/html/68bd57bd-1fd6-6c4e-4500-ed4726bc836e.htm
You have to convert your bitmapImage to a binary image represented as an 8 bpp image. Here is one way to do it.
UnmanagedImage grayImage = null;
if (image.PixelFormat == PixelFormat.Format8bppIndexed)
{
grayImage = bitmapImage;
}
else
{
grayImage = UnmanagedImage.Create(image.Width, image.Height, PixelFormat.Format8bppIndexed);
}

How to Use TextureBrush for painting an Image

Using GDI+ I am attempting to make a simple square that consists of an image. This rectangle will be moved. There are a few issues I've been running into. First of all, how to locally refer to the image (it is set to always copy), how to get the image centered in the square, and how to keep the image stationary when the square moves?
Bitmap runnerImage = (Bitmap)Image.FromFile(#"newRunner.bmp", true);//this results in an error without full path
TextureBrush imageBrush = new TextureBrush(runnerImage);
imageBrush.WrapMode = System.Drawing.Drawing2D.WrapMode.Clamp;//causes the image to get smaller/larger if movement is tried
Graphics.FillRectangle(imageBrush, displayArea);
Without using wrapMode.clamp it defaults to tiling, which looks like the image is tiled and moving the square moves from one image to the next
how to locally refer to the image (it is set to always copy)
You can add the image to a resource file and then reference that Image from there within the code. (See link http://msdn.microsoft.com/en-us/library/7k989cfy%28v=vs.90%29.aspx)
How to get the image centered in the square, and how to keep the image
stationary when the square moves?
This can be achieved using TranslateTransform with displayArea's location
(See link http://msdn.microsoft.com/en-us/library/13fy233f%28v=vs.110%29.aspx)
TextureBrush imageBrush = new TextureBrush(runnerImage);
imageBrush.WrapMode = WrapMode.Clamp;//causes the image to get smaller/larger if movement is tried
Rectangle displayArea = new Rectangle(25, 25, 100, 200); //Random values I assigned
Point xDisplayCenterRelative = new Point(displayArea.Width / 2, displayArea.Height / 2); //Find the relative center location of DisplayArea
Point xImageCenterRelative = new Point(runnerImage.Width / 2, runnerImage.Height / 2); //Find the relative center location of Image
Point xOffSetRelative = new Point(xDisplayCenterRelative.X - xImageCenterRelative.X, xDisplayCenterRelative.Y - xImageCenterRelative.Y); //Find the relative offset
Point xAbsolutePixel = xOffSetRelative + new Size(displayArea.Location); //Find the absolute location
imageBrush.TranslateTransform(xAbsolutePixel.X, xAbsolutePixel.Y);
e.Graphics.FillRectangle(imageBrush, displayArea);
e.Graphics.DrawRectangle(Pens.Black, displayArea); //I'm using PaintEventArgs graphics
Edit: I assumed that Image Size is always <= Square Size

Graphics.DrawPolygon not drawing at correct location

On the left is the polygon I drew using my paint-like program. On the right is the polygon drawn by System.Drawing.Bitmap/Graphics:
The code to draw it is as follows:
protected static Bitmap CropImage(Bitmap src, Vector2[] rect)
{
var result = new Bitmap(src.Width, src.Height);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
var pen = new Pen(Color.White);
g.DrawImage(src, new Point(0,0));
var poly = rect.Select(p => p.ToPointF()).ToArray();
g.DrawPolygon(pen, poly);
}
return result;
}
poly is:
{System.Drawing.PointF[4]}
[0]: {X = 57.4230042 Y = 57.4229736}
[1]: {X = 147.058868 Y = 56.0224}
[2]: {X = 148.43277 Y = 143.951767}
[3]: {X = 58.7969131 Y = 145.352341}
Each of the black squares in the image is 50x50. If you look at poly, all the coordinates are exactly as you'd expect: poly[0] is a little above 50,50 which corresponds to inside the black square labelled "6" (as shown in left image).
So how is Graphics getting confused and putting it in the wrong spot? It looks like it's scaling the whole rect down.
You are confused. The rectangle is correct, but the background image is scaled up. Note that the 6 and the black square are a lot bigger, but the rectangle is the exact same size.
Note that the Graphics.DrawImage method scales the source image to match the destination resolution.
This method draws an image using its physical size, so the image will have its correct size in inches regardless of the resolution (dots per inch) of the display device. For example, suppose an image has a pixel width of 216 and a horizontal resolution of 72 dots per inch. If you call this method to draw that image on a device that has a resolution of 96 dots per inch, the pixel width of the rendered image will be (216/72)*96 = 288.
You should use the overload that accepts a Rectangle instead:
g.DrawImage(src, new Rectangle(0, 0, src.Width, src.Height));

Categories