2
\$\begingroup\$

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:

  1. Do you see any potential memory leaks?
  2. Do you think code will be performing well when it comes to scalability
  3. Is there a component like ImageResizer that can do this?
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Instead of return new byte[0], I'd have a class-level field private static readonly byte[] emptyImage = new byte[0] and then just return emptyImage. You may also want to do similarly with your encoderParameters so it isn't created on each call as it seems to be invariant. \$\endgroup\$ Commented Apr 18, 2013 at 13:40

1 Answer 1

1
\$\begingroup\$

Expanding on my comment above, I'd also follow Microsoft's naming guidelines and have the method names be PascalCased. 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));
    }
}
\$\endgroup\$
2
  • \$\begingroup\$ You could do away with the extra calculations. ((img.Width / 2) + (img.Height / 2)) - ((img.Width / 2) - (img.Height / 2)) is equal to img.Height. Similarly ((img.Height / 2) + (img.Width / 2)) - ((img.Height / 2) - (img.Width / 2)) equals img.Width. \$\endgroup\$ Commented 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 or Height will wind up rounding down when said and done. I have edited my answer to show a simplified calculation that is accurate. \$\endgroup\$ Commented May 14, 2013 at 15:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.