How to Crop Image without changing the aspect ratio - c#

I need to crop an image without changing its aspect ratio. I am taking picture from CANON1100D using EDSDK. Captured image:
Width = 1920 and Height=1280
Aspect ratio is 1.5. But I need picture which aspect ratio will be 1.33.
// convert into processing resolution (1600,1200)
Image<Bgr, byte> runtime_frm = new Image<Bgr, byte>(frame.ToBitmap(1600,1200));
// also in bitmap processing
// Bitmap a = new Bitmap(runtime_frm.ToBitmap());
// Bitmap b = new Bitmap(a, new Size(1600,1200));
It's resizing the image, so the aspect ratio of image is changed, but it creates stress in image. I'd like to crop the image (1920x1280) to (1600x1200) in runtime.
How can I do this programmatically?

public void Crop(Bitmap bm, int cropX, int cropY,int cropWidth,int cropHeight)
{
var rect = new System.Drawing.Rectangle(cropX,cropY,cropWidth,cropHeight);
Bitmap newBm = bm.Clone(rect, bm.PixelFormat);
newBm.Save("image2.jpg");
}
Maybe something like that?
source

this is my solution for centered cropping.
Bitmap CenterCrop(Bitmap srcImage, int newWidth, int newHeight)
{
Bitmap ret = null;
int w = srcImage.Width;
int h = srcImage.Height;
if ( w < newWidth || h < newHeight)
{
MessageBox.Show("Out of boundary");
return ret;
}
int posX_for_centerd_crop = (w - newWidth) / 2;
int posY_for_centerd_crop = (h - newHeight) / 2;
var CenteredRect = new Rectangle( posX_for_centerd_crop,
posY_for_centerd_crop, newWidth, newHeight);
ret = srcImage.Clone(imageCenterRect, srcImage.PixelFormat);
return ret;
}

Related

Issue with horizontally resizing BMP Image (C#)

Link to Project: https://github.com/FFladenmuller/resize-bmp
Code works for a resize with a factor of 1. However, if trying a larger factor and I open the image, Photos says: "It looks like we don't support this file format".
I have not added in padding yet but I have only worked with images whose width is divisible by 4.
For loop to add BGR bytes to new Image:
for (int i = 54; i < oldBMP.Info.Count - 2; i += 3)
{
for(int j = 0; j < sizeMultiplier; j++)
{
newBMP.Info.Add(oldBMP.Info[i]);
newBMP.Info.Add(oldBMP.Info[i + 1]);
newBMP.Info.Add(oldBMP.Info[i + 2]);
}
}
First for loop to increment through BGR triples, second for loop to add each pixel sizeMultiplier amount of times.
Define the following method:
public static Bitmap ResizeImage(Bitmap image, Size size)
{
try
{
Bitmap result = new Bitmap(size.Width, size.Height);
using (Graphics g = Graphics.FromImage((Image)result))
{
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawImage(image, 0, 0, size.Width, size.Height);
}
return result;
}
catch
{
return image;
}
}
Then, within your code, whenever a resize is needed:
Bitmap image = new Bitmap(#"C:\Path\MyImage.bmp");
Single scaleWidth = 1.2f;
Int32 targetWidth = (Int32)((Single)image.Width * scaleWidth);
Single scaleHeight = 1.0f;
Int32 targetHeight = (Int32)((Single)image.Height * scaleHeight);
Size size = new Size(targetWidth, targetHeight);
Bitmap imageResized = ResizeImage(image, size);
An alternative (that has the drawback of reducing the output quality) is the following:
Bitmap image = new Bitmap(#"C:\Path\MyImage.bmp");
Single scaleWidth = 1.2f;
Int32 targetWidth = (Int32)((Single)image.Width * scaleWidth);
Single scaleHeight = 1.0f;
Int32 targetHeight = (Int32)((Single)image.Height * scaleHeight);
Bitmap imageResized = new Bitmap(image, targetWidth, targetHeight);

How to place maximum images in a blank Bitmap based on column/row input & margin variables and preserving aspect ratio of images

This might be a tricky question.
I will just cut to the chase.
First i make a blank Bitmap image:
Bitmap MasterImage = new Bitmap(PageSize.Width, PageSize.Height, PixelFormat.Format32bppArgb);
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(MasterImage);
Afterwards:
I get a parameter of how much images i want to place in the Blank Bitmap (page) based on columns and rows:
int numOfColumns=2; //(could be any value)
int numOfRows=4;
So we should get something like this:
I also get a parameter if i have some margin at top image and left image:
int PagetopMargin=0; //(could be any value)
int PageLeftMargin=0;
Than i have a variable called: imagesPath which is type of List<String> it contains full path of images.
Now i loop through images:
while (imagesPath.Count > 0)
{
Image image = Image.FromFile(imagesPath[0]);
//Logic comes here.
//after finishing processing and drawing images in Bitmap i remove image from list
imagesPath.RemoveAt(0);
}
What i am trying to achieve is how to place maximum images in that Bitmap page based on column/row and margin variables and preserving aspect ratio of images. (so images wont get distorted). if some images are left behind, its okey, i will continue next blank Bitmap to place them. just need to fill the Bitmap blank image to the full.
Here is a function to fit a rectangle into another, either centered or uncentered (aligned top-left):
Rectangle FitToBox(Rectangle scr, Rectangle dest, bool centered)
{
var ratioX = (double)dest.Width / scr.Width;
var ratioY = (double)dest.Height / scr.Height;
var ratio = Math.Min(ratioX, ratioY);
var newWidth = (int)(scr.Width * ratio);
var newHeight = (int)(scr.Height * ratio);
if (!centered)
return new Rectangle(0, 0, newWidth, newHeight);
else
return new Rectangle((dest.Width - newWidth) / 2,
(dest.Height - newHeight) / 2, newWidth, newHeight);
}
Its basic math is taken form this post.
Here is a test bed, centered and uncentered using different random values:
Random rnd = new Random();
int cols = rnd.Next(3) + 2;
int rows = rnd.Next(4) + 3;
int w = pan_dest.Width / cols;
int h = pan_dest.Height / rows;
using (Graphics G = pan_dest.CreateGraphics())
{
G.Clear(Color.White);
for (int c = 0; c < cols; c++)
for (int r = 0; r < rows; r++)
{
Rectangle rDest = new Rectangle(c * w, r * h, w, h);
Rectangle rSrc = new Rectangle(0, 0, rnd.Next(200) + 10, rnd.Next(200) + 10);
Rectangle rResult = FitToBox(rSrc, rDest, checkBox1.Checked);
Rectangle rDestPlaced = new Rectangle(c * w + (int)rResult.X,
r * h + rResult.Y, rResult.Width, rResult.Height);
using (Pen pen2 = new Pen(Color.SlateGray, 4f))
G.DrawRectangle(pen2, Rectangle.Round(rDest));
G.DrawRectangle(Pens.Red, rDestPlaced);
G.FillEllipse(Brushes.LightPink, rDestPlaced);
}
}
You would then draw your images like this:
G.DrawImage(someImageimg, rDestPlaced, rSrc, GraphicsUnit.Pixel);
This maximizes the image sizes, not the number of images you can fit on a page, as specified in your comment. For the latter you should look into something like 2-dimensional packing..

How to avoid bitmap out of memory when working on very large image for ie: 10.000.000 pixel and above

Currently i'm working on a system that load a very large image, with minimum width x heigh >= 10.000.000 pixel.
But the ratio of the user's upload image usually do not match our requirement ratio so i have to crop it to proper ratio, but when using System.Drawing bitmap to crop it, i always got SytemOutOfMemory exception.
I have try Bitmap.Clone and Graphic.DrawImage with correct RectangleF but no luck.
Is there anyways to do this without getting the outofmemory exception or are there any alternatives to System.Drawing library to get this task done easily ?
My code to load the image from user upload file:
var fileBinary = new byte[stream.Length];
stream.Read(fileBinary, 0, fileBinary.Length);
stream.Position = 0;
var fileExtension = Path.GetExtension(fileName);
using (Image image = Image.FromStream(stream, false, false))
{
//validation and check ratio
CropImage(image, PORTRAIT_RATIO, fileExtension);
}
And the CropImage function:
//Crop Image from center with predefine ratio
private byte[] CropImage(Image sourceImg, float ratio, string fileExtension)
var height = sourceImg.Height;
var width = sourceImg.Width;
var isPortrait = width < height;
RectangleF croppingRec = new RectangleF();
float positionX = 0;
float positionY = 0;
float cropHeight = (float)height;
float cropWidth = cropHeight * PORTRAIT_RATIO;
positionY = 0;
positionX = (width - cropWidth) / 2;
if (cropWidth > width)
{
cropWidth = width;
cropHeight = cropWidth * (1 / PORTRAIT_RATIO);
positionX = 0;
positionY = ((height - cropHeight) / 2);
}
croppingRec.Width = cropWidth;
croppingRec.Height = cropHeight;
croppingRec.X = positionX;
croppingRec.Y = positionY;
Bitmap bmpImage = sourceImg as Bitmap;
Bitmap bmpCrop = bmpImage.Clone(croppingRec, bmpImage.PixelFormat);
bmpCrop.Save("D:/test" + fileExtension, ImageFormat.Jpeg);
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(bmpCrop, typeof(byte[]));
}
}
You could convert the bitmap to a byte array. Try something like this (looks hackie but i don't know another way):
int pixelSize = 3;
int bytesCount = imgHeight * imgWidth * pixelSize;
byte[] byteArray= new byte[bytesCount];
BitmapData bitmapData = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, imgWidth, imgHeight), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Marshal.Copy(bitmapData.Scan0, byteArray, 0, bytesCount);
Each pixel in this array is represented by 3 bytes (this depends on the bitmap type). So you know that lenght of a bitmap line is 3 * imgWidth. Using this you could simply navigate in the byte array and copy just what you need into a new array.
You would then create a new bitmap with the desired final size, get the bitmap data and Marshal.Copy the new array into that:
Bitmap newBitmap = new Bitmap(Width, Height);
BitmapData newBitmapData = b.LockBits(BoundsRect,
ImageLockMode.WriteOnly,
newBitmap.PixelFormat);
Marshal.Copy(newByteArray, 0, newBitmapData.Scan0, newBytesCount);
Unlock the bitmaps at the end:
newBitmap.UnlockBits(newBitmapData );
bitmap.UnlockBits(bitmapData);
Hope this helps. Cheers.
Try using graphicsmagick.net library, then use the Crop method on MagickImage. It should still work well under asp.net, and handles huge image files using disk for scratch.

Resize width on upload and keep the height ratio proportional

I'm using the following system drawing code to resize images on Upload. The problem is that landscape or portrait images get distorted cause the sytem drawing is making them square. Is it possible to resize the width only and keep the height proportional? and How? Thanks
HttpPostedFile imageFile = UploadImages.PostedFile;
System.Drawing.Image ri = System.Drawing.Image.FromStream(imageFile.InputStream);
ri = ResizeBitmap((Bitmap) ri, 200, 200);
private Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
{
Bitmap result = new Bitmap(nWidth, nHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
g.DrawImage(b, 0, 0, nWidth, nHeight);
return result;
}
If what you want to do is create a new bitmap 200 pixels wide and with height scaled proportionately, you can do this:
private static int CalculateProportionalHeight(int oldWidth, int oldHeight, int newWidth)
{
if (oldWidth <= 0 || oldHeight <= 0 || newWidth <= 0)
// For safety.
return oldHeight;
double widthFactor = (double)newWidth / (double)oldWidth;
int newHeight = (int)Math.Round(widthFactor * (double)oldHeight);
if (newHeight < 1)
newHeight = 1; // just in case.
return newHeight;
}
private static Bitmap ResizeBitmap(Bitmap b, int nWidth)
{
int nHeight = CalculateProportionalHeight(b.Width, b.Height, nWidth);
Bitmap result = new Bitmap(nWidth, nHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
g.DrawImage(b, 0, 0, nWidth, nHeight);
return result;
}
Or are you looking to create a 200x200 bitmap with the old image scaled to fit inside, and letterboxed if necessary?
Update
If you are looking to create an image of a fixed 200x200 size, with the image scaled down proportionately to fit and letterboxed, this should do it:
static RectangleF PlaceInside(int oldWidth, int oldHeight, int newWidth, int newHeight)
{
if (oldWidth <= 0 || oldHeight <= 0 || newWidth <= 0 || newHeight <= 0)
return new RectangleF(oldWidth, oldHeight, newWidth, newHeight);
float widthFactor = (float)newWidth / (float)oldWidth;
float heightFactor = (float)newHeight / (float)oldHeight;
if (widthFactor < heightFactor)
{
// prefer width
float scaledHeight = widthFactor * oldHeight;
// new new RectangleF(x, y, width, height)
return new RectangleF(0, (newHeight - scaledHeight) / 2.0f, newWidth, scaledHeight);
}
else
{
// prefer height
float scaledWidth = heightFactor * oldWidth;
// new new RectangleF(x, y, width, height)
return new RectangleF((newWidth - scaledWidth) / 2.0f, 0, scaledWidth, newHeight);
}
}
private static Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
{
int oldWidth = b.Width;
int oldHeight = b.Height;
Bitmap result = new Bitmap(nWidth, nHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
{
var box = PlaceInside(oldWidth, oldHeight, nWidth, nHeight);
g.DrawImage(b, box);
}
return result;
}
Update 2
And here's a version that creates an image of width 200 and proportional height if landscape and height 200 and proportional width if portrait:
private static Bitmap ResizeBitmapUpto(Bitmap b, int nWidth, int nHeight, System.Drawing.Drawing2D.InterpolationMode interpolationMode)
{
int oldWidth = b.Width;
int oldHeight = b.Height;
var box = PlaceInside(oldWidth, oldHeight, nWidth, nHeight);
int actualNewWidth = (int)Math.Max(Math.Round(box.Width), 1);
int actualNewHeight = (int)Math.Max(Math.Round(box.Height), 1);
Bitmap result = new Bitmap(actualNewWidth, actualNewHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
{
g.InterpolationMode = interpolationMode;
g.DrawImage(b, 0, 0, actualNewWidth, actualNewHeight);
}
return result;
}
I added an interpolationMode so you can experiment with different qualities as per Ksv3n's answer.
(hopefully) Last Update
Here's the test setup I used to validate the code. I was able to open, resize and save a variety of images successfully on my computer.
public static void TestResizeBitmapUpto(string file, string newFile)
{
try
{
using (var image = Bitmap.FromFile(file))
{
if (image == null)
return;
using (Bitmap b = new Bitmap(image))
{
using (var newBitmap = ResizeBitmapUpto(b, 200, 200, System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor))
{
newBitmap.Save(newFile);
}
}
}
}
catch (System.IO.FileNotFoundException e)
{
Debug.WriteLine(e.ToString());
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
}
What's missing in your code is :
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
Here is the method you can use to resize your image with keeeping it proportional :
private Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
{
Bitmap result = new Bitmap(nWidth, nHeight);
using (Graphics g = Graphics.FromImage((System.Drawing.Image)result))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(b, 0, 0, nWidth, nHeight);
}
return result;
}
g.DrawImage will stretch the image to what you defined (in your case 200/200 which is a square).
You need to calculate the real values for nWidth and nHeight:
// original image (b.Width, b.Height)
double originalWidth = 200;
double originalHeight = 100;
// user defined wanted width
double wantedWidth = 200; // nWidth parameter to your method
double wantedHeight = 300; // nHeight parameter to your method
double ratioW = originalWidth / wantedWidth;
double ratioH = originalHeight / wantedHeight;
double ratio = Math.Max(ratioW, ratioH);
// rectangle proportional to the original that fits into the wanted
double destinationWidth = originalWidth / ratio; // what you pass to DrawImage as nWidth
double destinationHeight = originalHeight / ratio; // what you pass to DrawImage as nWidth
What it does is calculates the width and height ratio of the original and wanted image and takes the max of it. Uses that to divide the original values, which will make them fit perfectly inside the wanted rectangle.
This will draw the scaled image aligned top or left depending what is the orientation, since the resulting image will be equal or bigger then the wanted or original. To make it centered in the resulting image you would need to adjust left and top coordinates for DrawImage() by taking the difference in width/height and dividing by 2.
If the resulting image can be of different size then what the user specified (nWidth/nHeight) then you can simply initialize it with the destinationWidth/Height, and return that instead, without bothering for centering.

Display small image catalog in asp

Hi I want to put products to a catalog, How i can crop the picture to small and save the small and the orginal?
Try look here:
C# Thumbnail Image With GetThumbnailImage
or here:
Convert Bitmap to Thumbnail
Create thumbnail and reduce image size
Create thumbnail image
update
For example you can save the image to a folder and then you can do something like this:
var filename = "sourceImage.png";
using(var image = Image.FromFile(filename))
{
using(var thumbnail = image.GetThumbnailImage(20/*width*/, 40/*height*/, null, IntPtr.Zero))
{
thumbnail.Save("thumb.png");
}
}
update
to resize proportionally try something like this:
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;
}

Categories

Resources