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.
Related
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);
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.
It possible get cutout of image data.
If I know:
byte[] ImageData;
int width;
int height;
Basically I try find how get inner section of image from byte[] source.
For example I have image which is w: 1000px and h: 600px. And I want byte[] middle section 200*200px in byte[].
First of all you need to know how many bytes in your array represent one pixel. The following assumes that you have an RGB image with 3 bytes per pixel.
Then, the array-index of the first byte that represents the top-left corner of your cutout is represented as
int i = y * w + x
where y is the y-coordinate of the cutout, w is the width of the entire image and x is the x coordinate of the cutout.
Then, you can do as follows:
// cw: The width of the cutout
// ch: The height of the cutout
// x1/y1: Top-left corner coordinates
byte[] cutout = new byte[cw * ch * 3]; // Byte array that takes the cutout bytes
for (int cy = y1; cy < y2; cy++)
{
int i = cy * w + x1;
int dest = (cy - y1) * cw * 3;
Array.Copy(imagebytes, i, cutout, dest, cw * 3);
}
This iterates from the first to the last row to be cut out. Then, in i, it calculates the index of the first byte of the row in the image that should be cutout. In dest it calculates the index in cutout to which the bytes should be copied.
After that it copies the bytes for the current row to be cut out into cutout at the specified position.
I have not tested this code, really, but something like that should work. Also, please note that there's currently no range checking - you need to make sure that the location and dimensions of the cutout are really within the bounds of the image.
If you can convert it to an an image first, you can use this code I found on Bytes.Com
The following code works for me. It loads a .gif, draws the 30 x 30
section of the gif into an offscreen bitmap, and then draws the scaled
image into a picturebox.
System.Drawing.Image img=... create the image from the bye array ....
Graphics g1 = pictureBox1.CreateGraphics();
g1.DrawImage(img, 0, 0, img.Width, img.Height);
g1.Dispose();
Graphics g3 = Graphics.FromImage(bmp);
g3.DrawImageUnscaled(img, 0, 0, bmp.Width, bmp.Height);
Graphics g2 = pictureBox2.CreateGraphics();
g2.DrawImageUnscaled(bmp, 0, 0, bmp.Width, bmp.Height);
g2.Dispose();
g3.Dispose();
img.Dispose();
You can use this question to turn your byte[] into an image : Convert a Byte array to Image in c# after modifying the array
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);
}
I have this code to resize an image but the image doesn't look so good:
public Bitmap ProportionallyResizeBitmap(Bitmap src, int maxWidth, int maxHeight)
{
// original dimensions
int w = src.Width;
int h = src.Height;
// Longest and shortest dimension
int longestDimension = (w > h) ? w : h;
int shortestDimension = (w < h) ? w : h;
// propotionality
float factor = ((float)longestDimension) / shortestDimension;
// default width is greater than height
double newWidth = maxWidth;
double newHeight = maxWidth / factor;
// if height greater than width recalculate
if (w < h)
{
newWidth = maxHeight / factor;
newHeight = maxHeight;
}
// Create new Bitmap at new dimensions
Bitmap result = new Bitmap((int)newWidth, (int)newHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
g.DrawImage(src, 0, 0, (int)newWidth, (int)newHeight);
return result;
}
Try setting the InterpolationMode of the graphics object to some value such as HighQualityBicubic. This should ensure that resize/scaled image looks much better than the "default".
So, in the code you have posted, instead of doing:
// Create new Bitmap at new dimensions
Bitmap result = new Bitmap((int)newWidth, (int)newHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
g.DrawImage(src, 0, 0, (int)newWidth, (int)newHeight);
return result;
Try doing this instead:
// Create new Bitmap at new dimensions
Bitmap result = new Bitmap((int)newWidth, (int)newHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(src, 0, 0, (int)newWidth, (int)newHeight);
}
return result;
(Note the line setting the InterpolationMode property to one of the InterpolationMode enumeration's values).
Please see this link:
How to: Use Interpolation Mode to Control Image Quality During Scaling
For further information on controlling the quality of an image when resizing/scaling.
Also see this CodeProject article:
Resizing a Photographic image with GDI+ for .NET
For information of the different visual effects the various InterpolationMode enumeration settings will have on an image. (About two-thirds of the way down the article, under the section entitled, "One last thing...").
If you need an on-the-fly resize I suggest you to try an HttpHandler I write recently, I've posted the code on my blog (sorry, but is in italian).
With some modifies you can use the code also for save the transformed image on the disk.