I've coded following two methods to combine images that are HTTP POSTed to the server:
// Crops two squares out of two separate images
// And then combines them into single image that's returned as byte[]
private byte[] combineImages(HttpPostedFileBase[] itemPayload)
{
try
{
using (var ms = new MemoryStream())
using (var bitmap = new Bitmap(800, 400, PixelFormat.Format24bppRgb))
using (var img1 = Image.FromStream(itemPayload[0].InputStream))
using (var img2 = Image.FromStream(itemPayload[1].InputStream))
using (var g = Graphics.FromImage(bitmap))
{
RectangleF img1Params = getCropParams(img1);
g.DrawImage(img1, new RectangleF(0, 0, 400, 400), img1Params, GraphicsUnit.Pixel);
RectangleF img2Params = getCropParams(img2);
g.DrawImage(img2, new RectangleF(400, 0, 400, 400), img2Params, GraphicsUnit.Pixel);
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
bitmap.Save(ms, ImageCodecInfo.GetImageDecoders().Single(codec => codec.FormatID == ImageFormat.Jpeg.Guid), encoderParameters);
return ms.ToArray();
}
}
catch (Exception ex)
{
return new byte[0];
}
}
private RectangleF getCropParams(Image img)
{
var rect = new RectangleF();
if (img.Width > img.Height)
{
rect.X = (img.Width / 2) - (img.Height / 2);
rect.Width = (img.Width / 2) + (img.Height / 2) - rect.X;
rect.Y = 0;
rect.Height = img.Height;
}
else
{
rect.X = 0;
rect.Width = img.Width;
rect.Y = (img.Height / 2) - (img.Width / 2);
rect.Height = (img.Height / 2) + (img.Width / 2) - rect.Y;
}
return rect;
}
It produces results I expect, but I was curious if my implementation could be improved. I am specifically interested in three things:
- Do you see any potential memory leaks?
- Do you think code will be performing well when it comes to scalability
- Is there a component like ImageResizer that can do this?
1 Answer 1
Expanding on my comment above, I'd also follow Microsoft's naming guidelines and have the method names be PascalCase
d. Plus, I'm not a huge fan of mutable structs
, preferring to initialize them via provided constructors. So I came up with the following:
public static class ImageCombiner
{
private static readonly EncoderParameters encoderParameters = new EncoderParameters(1);
private static readonly RectangleF sizeLocation1 = new RectangleF(0.0F, 0.0F, 400.0F, 400.0F);
private static readonly RectangleF sizeLocation2 = new RectangleF(400.0F, 0.0F, 400.0F, 400.0F);
private static readonly ImageCodecInfo jpgDecoder = ImageCodecInfo.GetImageDecoders().Single(codec => codec.FormatID == ImageFormat.Jpeg.Guid);
private static readonly byte[] emptyImage = new byte[0];
static ImageCombiner()
{
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
}
// Crops two squares out of two separate images
// And then combines them into single image that's returned as byte[]
public static byte[] CombineImages(IList<HttpPostedFileBase> itemPayload)
{
try
{
using (var ms = new MemoryStream())
using (var bitmap = new Bitmap(800, 400, PixelFormat.Format24bppRgb))
using (var img1 = Image.FromStream(itemPayload[0].InputStream))
using (var img2 = Image.FromStream(itemPayload[1].InputStream))
using (var g = Graphics.FromImage(bitmap))
{
g.DrawImage(img1, sizeLocation1, GetCropParams(img1), GraphicsUnit.Pixel);
g.DrawImage(img2, sizeLocation2, GetCropParams(img2), GraphicsUnit.Pixel);
bitmap.Save(ms, jpgDecoder, encoderParameters);
return ms.ToArray();
}
}
catch
{
return emptyImage;
}
}
private static RectangleF GetCropParams(Image img)
{
return img.Width > img.Height
? new RectangleF(
(img.Width / 2) - (img.Height / 2),
0.0F,
2 * (img.Height / 2),
img.Height)
: new RectangleF(
0.0F,
(img.Height / 2) - (img.Width / 2),
img.Width,
2 * (img.Width / 2));
}
}
-
\$\begingroup\$ You could do away with the extra calculations.
((img.Width / 2) + (img.Height / 2)) - ((img.Width / 2) - (img.Height / 2))
is equal toimg.Height
. Similarly((img.Height / 2) + (img.Width / 2)) - ((img.Height / 2) - (img.Width / 2))
equalsimg.Width
. \$\endgroup\$Tanzeel Kazi– Tanzeel Kazi2013年04月20日 01:49:16 +00:00Commented Apr 20, 2013 at 1:49 -
\$\begingroup\$ @TanzeelKazi while you are correct the calculations can be simplified, what you say they can be simplified to is somewhat off. For instance, an image with an odd
Width
orHeight
will wind up rounding down when said and done. I have edited my answer to show a simplified calculation that is accurate. \$\endgroup\$Jesse C. Slicer– Jesse C. Slicer2013年05月14日 15:01:34 +00:00Commented May 14, 2013 at 15:01
return new byte[0]
, I'd have a class-level fieldprivate static readonly byte[] emptyImage = new byte[0]
and then justreturn emptyImage
. You may also want to do similarly with yourencoderParameters
so it isn't created on each call as it seems to be invariant. \$\endgroup\$