Position and size of combined images inside a new Bitmap - c#

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

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;

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

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.

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

How to properly cope with Memory residing Bitmap resize

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

Converting from a Format8bppIndexed to a Format24bppRgb in C#/GDI+

Alright, I have an image coming through from an external application in an 8-bit indexed format. I need this image converted to a 24-bit format of the exact same size.
I've tried creating a new Bitmap of the same size and of type Format24bppRgb and then using a Graphics object to draw the 8-bit image over it before saving it as a Bmp. This approach doesn't error out but when I open the resulting image the BMP header has all kinds of funky values. The height and width are HUGE and, in addition, there are funny (and large) values for the compression flags and a few others. Unfortunately my particular requirements are to pass this file off to a specific printer driver that demands a 24-bit image with specific header values (which I'm trying to achieve through GDI+)
Anyone know of an example on "up-converting" an indexed file to a not-indexed 24-bit file? If not an example, which path should I start down to write my own?
-Kevin Grossnicklaus
kvgros#sseinc.com
I used the code below to "up-convert" an image from 8bpp to 24bpp. Inspecting the generated 24bpp file with a hex editor and comparing against the 8bpp file shows no difference in height and width in the two files. That is, the 8bpp image was 1600x1200, and the 24bpp image has the same values.
private static void ConvertTo24(string inputFileName, string outputFileName)
{
Bitmap bmpIn = (Bitmap)Bitmap.FromFile(inputFileName);
Bitmap converted = new Bitmap(bmpIn.Width, bmpIn.Height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(converted))
{
// Prevent DPI conversion
g.PageUnit = GraphicsUnit.Pixel
// Draw the image
g.DrawImageUnscaled(bmpIn, 0, 0);
}
converted.Save(outputFileName, ImageFormat.Bmp);
}
Everything else in the headers looks reasonable, and the images display identical on my system. What "funky values" are you seeing?
This is my conversion code. Notice the matching of resolution between source image and resulting image.
private void ConvertTo24bppPNG(Stream imageDataAsStream, out byte[] data)
{
using ( Image img = Image.FromStream(imageDataAsStream) )
{
using ( Bitmap bmp = new Bitmap(img.Width, img.Height, PixelFormat.Format24bppRgb) )
{
// ensure resulting image has same resolution as source image
// otherwise resulting image will appear scaled
bmp.SetResolution(img.HorizontalResolution, img.VerticalResolution);
using ( Graphics gfx = Graphics.FromImage(bmp) )
{
gfx.DrawImage(img, 0, 0);
}
using ( MemoryStream ms = new MemoryStream() )
{
bmp.Save(ms, ImageFormat.Png);
data = new byte[ms.Length];
ms.Position = 0;
ms.Read(data, 0, (int) ms.Length);
}
}
}
}
It seems odd that you're creating a Bitmap of the same width and height as your input, yet the generated BMP is much larger. Can you post some code?
The problem is probably the difference between the Vertical- and HorizontalResolution of your source image and your output image. If you load a 8bpp indexed bitmap with a resolution of 72 DPI, and then create a new 24bpp bitmap (default resolution will be 96 DPI... at least it is on my system) and then use Graphics.DrawImage to blit to the new bitmap, your image will appear slightly zoomed in and cropped.
Having said that, I don't know off the top of my head how to properly create the output Bitmap and/or Graphics object to scale properly when saved. I suspect it will have something to do with creating the images using a common scale like inches instead of pixels.

Categories