Sunday, July 29, 2012

Generating Images On-The-Fly

Generating images on the fly is very simple in .NET, I can see a few uses to it, serving the client images with watermarks (its very slow, you should cache the results!), generating logos, making text harder to parse by search engines and I'm sure I didn't even scratch the surface.

I've created a small demo, it includes uploading an image, creating a new one containing the uploaded image and some text.

It doesn't handle long text, so don't expect too much from this demo but it does give you the basics.

Here's the whole action.


public ActionResult Process(HttpPostedFileBase file, string text)
{
    string error = string.Empty;

    //get file
    string filename = string.Empty;
    Stream filecontents = null;

    if ((file == null) || (file.ContentLength == 0))
        error = "No file uploaded";
    else
    {
        //get filename
        filename = file.FileName;
        filecontents = file.InputStream;
    }

    Bitmap uploadedImage = null;

    //check valid extensions
    if (!string.IsNullOrEmpty(filename))
    {
        if (!validextensions.Contains(Path.GetExtension(filename)))
            error = "File is not an image";
        else
        {
            //attempt to load image
            uploadedImage = new Bitmap(filecontents);
        }
    }

    //if there's an error, draw it on the image
    if (!string.IsNullOrEmpty(error))
    {
        uploadedImage = new Bitmap(200, 200);
        var g = Graphics.FromImage(uploadedImage);
        g.DrawString(error, new Font("Arial", 8), new SolidBrush(Color.Red), new PointF(1, 1));
    }

    //create new image
    Bitmap newimage = new Bitmap(400, 200);
    var graphics = Graphics.FromImage(newimage);

    //blend into image instead of overriding, this way we can use trasnparent PNGs instead of ignoring the transparency.
    graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;

    //lets get the relative size so it will keep its ratio
    var width = 200;
    var height = 200;

    float ratio = (float)uploadedImage.Width / (float)uploadedImage.Height;
    if (ratio < 1)
        width = (int)(width * ratio);
    else
        height = (int)(height / ratio);

    //lets center the image
    var top = (200 - height) / 2;
    var left = (200 - width) / 2;

    //draw original file
    graphics.DrawImage(uploadedImage, new Rectangle(left, top, width, height));

    //lets determine the size of the string;
    var sizeofString = graphics.MeasureString(text, new Font("Arial", 10));

    //lets center the text
    var texttop = (200 - sizeofString.Height) / 2;
    var textleft = (200 - sizeofString.Width) / 2;

    //add text
    graphics.DrawString(text, new Font("Arial", 10), new SolidBrush(Color.Black), new PointF(textleft + 200, texttop));


    //send to client
    Response.ContentType = "image/png"; 
    newimage.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Png);

    return new EmptyResult();
}


The code is self explanatory but we'll review it anyway.

1. First thing to take note of is the enctype="multipart/form-data" in the form element, it allows files to be uploaded.

2. Using the HttpPostedFileBase class to access the uploaded file.

3. Validating the file extensions.

4. If an error occured, we're drawing the error message on the image with DrawString.

5. Setting CompositingMode to SourceOver.

6. Calculating the relative maximum size and position for the uploaded image.

7. Drawing the uploaded image with DrawImage.

8. Mesuring the string size with MeasureString, so we can center it.

9. Setting the content type.

10. Saving the result Bitmap to the response stream with the selected ImageFormat.

11. Returning EmptyResult to stop MVC from processing this method.

You can find the demo project at:
https://github.com/drorgl/ForBlog/tree/master/ImageGenerator

Note: some code was written inefficiently for the sake of demo and ease of understanding, for example, CompositingMode and multiple creation of brushes and fonts.

No comments:

Post a Comment