Resize and Fit Image in WPF - c#

I have the following:
using (bmp == new Bitmap(50, 50)) {
using (g == Graphics.FromImage(bmp)) {
g.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic;
g.DrawImage(img, 0, 0, 50, 50);
}
}
The user provides my app an image which my app has to resize and also fit the entire image.
The problem with the above code is that it stretches the image, not fit (as a 4:3 movie fits in a widescreen TV leaving blackbars).
Does anyone have any solution for this? Also, I would prefer not to use GDI.

You need to take the size of the input image into account. Here is a code snippet that should get you into the right direction:
int x1 = 0, y1 = 0, x2 = 50, y2 = 50;
if (img. Width <= img.Height)
{
// compute x1, y1 to fit img horizontally into bmp
}
else
{
// compute y1, y2 to fit img vertically into bmp
}
g.DrawImage(img, x1,y1, x2,y2);
Also notice that you are asking for resizing an image in WPF but using System.Drawing which uses GDI+ under the hood.

Related

Transforming Rectangle Coordinates to a Large Resolution Image

I'm using the following code to Transform a small rectangle coordinates to a larger one ie: A rectangle position on a small image to the same position on the larger resolution of the same image
Rectangle ConvertToLargeRect(Rectangle smallRect, Size largeImageSize, Size smallImageSize)
{
double xScale = (double)largeImageSize.Width / smallImageSize.Width;
double yScale = (double)largeImageSize.Height / smallImageSize.Height;
int x = (int)(smallRect.X * xScale + 0.5);
int y = (int)(smallRect.Y * yScale + 0.5);
int right = (int)(smallRect.Right * xScale + 0.5);
int bottom = (int)(smallRect.Bottom * yScale + 0.5);
return new Rectangle(x, y, right - x, bottom - y);
}
But there seems to be a problem with some images.The transformed rectangle coordinates seems to be off the image.
UPDATE:
img.Draw(rect, new Bgr(232, 3, 3), 2);
Rectangle transret= ConvertToLargeRect(rect, orgbitmap.Size, bit.Size);
target = new Bitmap(transret.Width, transret.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(orgbitmap, new Rectangle(0, 0, target.Width, target.Height),
transret, GraphicsUnit.Pixel);
}
Rectangle Drawn on small resolution Image
{X=190,Y=2,Width=226,Height=286}
Rectangle Transformed into Orginal Large Resolution Image {X=698,Y=7,Width=830,Height=931}
Original Image
First of all, if you resize the shape it shouldn't move position. That's not what one would expect out of enlarging a shape. This means the X,Y point of the top-left corner shouldn't be transformed.
Second, you shouldn't be adding 0.5 manually to operations, that's not a clean way to proceed. Use the ceiling function as suggested by #RezaAghaei
Third, you should not substract X/Y from the height/width, your calculations should be done as width * scale.
Please correct those mistakes, and if it doesn't work I'll update the answer with extra steps.

How to crop image into a square

Hi I'm trying to crop images that users upload onto my site into a square. I have already tried a few solutions posted on this website, namely http://stackoverflow.com/questions/5222711/image-resize-in-c-sharp-algorith-to-determine-resize-dimensions-height-and-wi and WebImage Crop To Square. However, although these solutions convert the image into a square, they add large areas of transparency on the top and bottom of the image, which is not what I want since these images will be used as profile images.
This is the code I have always used in my websites:
public Bitmap MakeSquarePhoto(Bitmap bmp, int size)
{
try
{
Bitmap res = new Bitmap(size, size);
Graphics g = Graphics.FromImage(res);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, size, size);
int t = 0, l = 0;
if (bmp.Height > bmp.Width)
t = (bmp.Height - bmp.Width) / 2;
else
l = (bmp.Width - bmp.Height) / 2;
g.DrawImage(bmp, new Rectangle(0, 0, size, size), new Rectangle(l, t, bmp.Width - l * 2, bmp.Height - t * 2), GraphicsUnit.Pixel);
return res;
}
catch { }
}
To crop an image without adding large patches of transparency to the top and bottom, you are going to have to cut off part of the sides. Without seeing the code it should be something along the lines of cutting off (width-height)/2 pixels from each side.

C#: Image resize and crop at the same time

I'm trying to write a very fast method for creating thumbnails. It will crop and resize an image at the same time. The result image will always be square-like. So having that in mind we can now look at my code :) It is working (as far as I can see):
public static void CreateAndSaveThumbnail(string path, string output, int desiredSize)
{
using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(path)))
using (Bitmap src = new Bitmap(ms))
{
Rectangle crop = src.Width > src.Height ?
new Rectangle((src.Width - src.Height) / 2, 0, src.Height, src.Height) :
new Rectangle(0, 0, src.Width, src.Width);
int size = Math.Min(desiredSize, crop.Width);
using (Bitmap cropped = src.Clone(crop, src.PixelFormat))
using (Bitmap resized = new Bitmap(size, size))
using (Graphics g = Graphics.FromImage(resized))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(cropped, 0, 0, size, size);
resized.Save(output, ImageFormat.Jpeg);
}
}
}
So how the code above works? It crops the middle part for landscape images and top part for portrait images. So this is a standard behavior. Then it resizes the cropped image (it is already a square).
Is this code OK? Could it be faster? How? Any optimizations possible?
Your routine works great but for one slight problem, at least in my desired application. I wanted the routine to crop portrait bitmaps the same as you do for landscape bitmaps; by cropping off half of the excess from the top and half of excess from the bitmap. So I simply duplicated the landscape crop code to the portrait crop code and adjusted as necessary resulting in this:
Rectangle crop = src.Width > src.Height ?
new Rectangle((src.Width - src.Height) / 2, 0, src.Height, src.Height) :
new Rectangle(0, (src.Height - src.Width) / 2, src.Width, src.Width);

Issues with DrawString on a Bitmap

I have this constructor that takes in an input of an array of Color objects and some misc strings (like file location and size of list), and stores this heatmap into a bitmap instance called _image_. Everything works out fine, but my main issue is I have no way to render text onto this heatmap. I would like to overlay title text and x and y-axis labels onto this bitmap.
public HeatMap(IEnumerable<Color> colors, int width, int height, string file, int U, int V) {
if (colors == null)
throw new ArgumentNullException("colors");
if (width <= 0)
throw new ArgumentException("width must be at least 1");
if (height <= 0)
throw new ArgumentException("height must be at least 1");
_width = width;
_height = height;
_file = file;
_image = new Bitmap(U, V, PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(_image);
graphics.Clear(Color.White);
graphics.Dispose();
int x = 0;
int y = 0;
foreach (Color color in colors) {
_image.SetPixel(x, y, color); // This can be speeded up by using GH_MemoryBitmap if you want.
y++;
if (y >= V) {
y = 0;
x++;
}
if (x >= U)
break;
}
}
Below, I also have a method that resizes the image (without too much distortion), and I figured I might as well make use of this since I have a graphics object I can use, like so:
private Bitmap ResizeBitmap(Bitmap sourceBMP, int width, int height) {
Bitmap result = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(result)) {
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(sourceBMP, 0, 0, width, height);
g.DrawString("A heatmap.", SystemFonts.DefaultFont, Brushes.Black, 10, 10);
}
return result;
}
Using the above, I was able to overlay text over it, as a starting point. My question is, how do I add a white boarder over the above image (so my text doesn't overlap my graph), and then overlay x and y-axis text onto it?
I guess my specific question really is - how do I render text onto, say, every four columns of my heatmap? In most instances, there are 100 x-axis objects, and like 24 y-axis objects. The y-axis would have the time of the day, while the x-axis has the day of the year. My familiarity with using graphics in C# is very low, so any pointers is very appreciated.
You've to use the DrawString before the graphics.Dispose() and after the Clear().
Graphics graphics = Graphics.FromImage(_image);
graphics.Clear(Color.WhiteSmoke);
graphics.DrawString("your text", SystemFonts.DefaultFont, Brushes.Black, new PointF(0, 0));
graphics.Dispose();
Where the PointF(0, 0) represents the up-left corner of your text placeholder, you've to calculate the text position based on the positions of your heatmap.
UPDATE (alignment)
You can align your text in relation with a virtual rectangle, doing something like:
Graphics graphics = Graphics.FromImage(_image);
graphics.Clear(Color.WhiteSmoke);
StringFormat stringFormat = new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
graphics.DrawString("your text", SystemFonts.DefaultFont, Brushes.Black, new RectangleF(0, 0, 100, 100), stringFormat);
graphics.Dispose();
In this case I've created a virtual box of 100x100 positioned on the top-left corner, and the text is h/v centered relatively to this box.

I'm experiencing unexpected results from Graphics.DrawImage

To reproduce this issue, please create a 2x2 pixel black image in Microsoft Paint, saved as D:\small.png. Then create a new WinForms app in Visual Studio, with a no-margin PictureBox. Then use the following code:
void f6(Graphics g)
{
var img = Image.FromFile(#"d:\small3.png");
var srcRect = new Rectangle(0, 0, img.Width, img.Height);
int factor = 400;
var destRect = new Rectangle(0, 0, img.Width * factor, img.Height * factor);
g.DrawRectangle(new Pen(Color.Blue), destRect);
g.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
f6(e.Graphics);
}
I expect the entire rectangle inside the blue margins be black while the output is as follows:
Why is this happening?
ok, thanks. i din't know about interpolation. now, let's change the code as following:
void f6(Graphics g)
{
var img = Image.FromFile(#"d:\small3.png");
var srcRect = new Rectangle(0, 0, img.Width, img.Height);
int factor = 200;
var destRect = new Rectangle(0, 0, img.Width * factor, img.Height * factor);
g.FillRectangle(new SolidBrush(Color.DarkCyan), pictureBox1.ClientRectangle);
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.DrawRectangle(new Pen(Color.Blue), destRect);
g.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}
it produces the following result:
Result3
which is still unacceptable.
i've tried 60x60 images too. the problem is not because it's a 2x2 image. they produce the same effect. the problem is that why GDI+ decides not to fill the entire destRect with the entire srcRect?!
the original problem is that i've a big image tiled in smaller ones. i need adjacent tiles neither overlap nor seam exist between them. in C++, StretchBlt works properly. but it doesn't produce a smooth stretched image.
GDI+'s definition of the source rectangle is a bit odd.
(0,0) in the source image is actually the center of the upper-left pixel in the image. (width-1,height-1) is the center of the lower-right pixel in the image.
That means that the upper-left pixel is the rectangle from (-0.5,-0.5) to (0.5,0.5), and the lower-right pixel is the rectangle from (width-1.5,height-1.5) to (width-0.5,height-0.5). Thus, your source rectangle is actually outside the image by 0.5 pixels to the right and bottom.
So, you actually need a source rectangle of (-0.5, -0.5, img.Width, img.Height).
I guess you can also try setting PixelOffsetMode as Hans suggests. That would actually make sense of the behavior, but I wouldn't have expected it to apply to source rectangles.
Looks like the image was resized using bicubic interpolation. The normal Bicubic sizing algorithm normally requires 16 pixels to interpolate one, but you only have 4 in the image, so the remaining 12 pixels are never set, staying white.
Try changing the resizing method to nearest-neighbor. This is done by setting the InterpolationMode property of your Graphics object g to InterpolationMode.NearestNeighbor:
void f6(Graphics g)
{
var img = Image.FromFile(#"d:\small3.png");
var srcRect = new Rectangle(0, 0, img.Width, img.Height);
int factor = 400;
var destRect = new Rectangle(0, 0, img.Width * factor, img.Height * factor);
g.ImterpolatonMode = InterpolationMode.NearestNeighbor;
g.DrawRectangle(new Pen(Color.Blue), destRect);
g.DrawImage(img, destRect, srcRect, GraphicsUnit.Pixel);
}

Categories