About the author

Miron Abramson
Me
Software Engineer,
CTO at PixeliT
and .NET addicted for long time.
Open source projects:
MbCompression - Compression library

Recent comments

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2014

Creative Commons License

Blog Flux Directory
Technology Blogs - Blog Top Sites

Resize images 'on the fly' while uploading

Lot of sites (specialy in the web 2.0 times) allows the user to upload images to the site. To save disk space and for visual design reasons, it is better to have the images is small size and specified dimensions. This can be made in two ways. One way is to tell the users to resize the images before uploading them, and the more 'User friendly' way is to let the user upload images with any size and dimensios, and resize them while the uploading.

Here is the code how can it be done:

First we define enum with the resize options:

 public enum ResizeOptions
{
    // Use fixed width & height without keeping the proportions
    ExactWidthAndHeight,

    // Use maximum width (as defined) and keeping the proportions
    MaxWidth,

    // Use maximum height (as defined) and keeping the proportions
    MaxHeight,

    // Use maximum width or height (the biggest) and keeping the proportions
    MaxWidthAndHeight
}

Second, the 'resize' method:

 public static System.Drawing.Bitmap DoResize(System.Drawing.Bitmap originalImg, int widthInPixels, int heightInPixels)
{
       System.Drawing.Bitmap bitmap;
       try
       {
           bitmap = new System.Drawing.Bitmap(widthInPixels, heightInPixels);
           using (System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage(bitmap))
           {
               // Quality properties
               graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
               graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
               graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
               graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;

               graphic.DrawImage(originalImg, 0, 0, widthInPixels, heightInPixels);
               return bitmap;
           }
       }
       finally
       {
           if (originalImg != null)
           {
               originalImg.Dispose();
           }
       }
}

Third, the method that calculate the dimensions according to the enum value that passed:

 public static System.Drawing.Bitmap ResizeImage(System.Drawing.Bitmap image, int width, int height, ResizeOptions resizeOptions)
{
    float f_width;
    float f_height;
    float dim;
    switch (resizeOptions)
    {
        case ResizeOptions.ExactWidthAndHeight:
            return DoResize(image, width, height);

        case ResizeOptions.MaxHeight:
            f_width = image.Width;
            f_height = image.Height;

            if (f_height <= height)
                return DoResize(image, (int)f_width, (int)f_height);

            dim = f_width / f_height;
            width = (int)((float)(height) * dim);
            return DoResize(image, width, height);

        case ResizeOptions.MaxWidth:
            f_width = image.Width;
            f_height = image.Height;

            if (f_width <= width)
                return DoResize(image, (int)f_width, (int)f_height);

            dim = f_width / f_height;
            height = (int)((float)(width) / dim);
            return DoResize(image, width, height);

        case ResizeOptions.MaxWidthAndHeight:
            int tmpHeight = height;
            int tmpWidth = width;
            f_width = image.Width;
            f_height = image.Height;

            if (f_width <= width && f_height <= height)
                return DoResize(image, (int)f_width, (int)f_height);

            dim = f_width / f_height;

            // Check if the width is ok
            if (f_width < width)
                width = (int)f_width;
            height = (int)((float)(width) / dim);
            // The width is too width
            if (height > tmpHeight)
            {
                if (f_height < tmpHeight)
                    height = (int)f_height;
                else
                    height = tmpHeight;
                width = (int)((float)(height) * dim);
            }
            return DoResize(image, width, height);
        default:
            return image;
    }
}

Last thing is to connect it all to the uploading event: (Our uploading control called 'fuPhoto' (Normal 'FileUpload' control))

 public bool UploadFile()
{
    // Make some validatins check (control have file or extension check...)

    int width = 300;
    int height = 300;

    // Do the resize, and save the file with desired name
    using( System.Drawing.Bitmap img = ResizeImage(new System.Drawing.Bitmap(fuPhoto.PostedFile.InputStream), width, height, ResizeOptions.MaxWidthAndHeight))
    {
        string destination = Server.MapPath("~") + Path.GetFileName(FileUpload1.FileName);
        switch (Path.GetExtension(fuPhoto.FileName).ToLower())
        {
            case ".gif":
                img.Save(destination, System.Drawing.Imaging.ImageFormat.Gif);
                break;
            case ".jpg":
            default:
                img.Save(destination, System.Drawing.Imaging.ImageFormat.Jpeg);
                break;
        }
    }
}

Update:

Some readers asked my about how can this code be used to resize the uploaded image and than save it in to the DB and not to the disk. To save the image to the DB we need to convert it into byte[] (That is how it needs to be sent to the Store Procedre). This can be done in some way. one way is:

 public static byte[] ImageToArray(Bitmap image, ImageFormat format)
 {
       using(MemoryStream mem = new MemoryStream())
       {
           mem.Position = 0;
           image.Save(mem, format);
           return mem.ToArray();
       }
  }

 Upload the file using the method 'ResizeImage', than convert the returned Bitmap into byte[] using this method, and send it to your Store Procedure.

done 

Currently rated 4.5 by 15 people

  • Currently 4.466667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET | C#
Posted by Miron on Sunday, October 28, 2007 1:24 AM
Permalink | Comments (29) | Post RSSRSS comment feed

Hightlight rows on OnMoueover event in GridView

A nice and simple functionality can make big visual different.

For example: Highlight rows on OnMouseOver event in GridView will make it much nicer. All you need to do is to execute the following method on the 'OnRowDataBound' event  in the Grid:

protected void HighLightRow(GridViewRowEventArgs e, string OriginalColor, string OnMouseOverColor,
    string OriginalFontColor, string onMouseOverFontColor)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        e.Row.Attributes.Add("onmouseover", "this.style.background='" + OnMouseOverColor +
            "';this.style.color='" + onMouseOverFontColor + "';");
        e.Row.Attributes.Add("onmouseout", "this.style.background='" + OriginalColor +
            "';this.style.color='" + OriginalFontColor + "';");
    }
}

Ofcourse you can play with it and make it even nicer.

Implementation:  Drop the method above in your page, BasePage or whereevr, add an event handler 'OnRowDataBound' to your  GidView and call the HighLightRow method from this event. For example:

In the aspx:
<asp:GridView ID="GridView1" runat="server" OnRowDataBound="GridView_RowDataBound"> ...

in the code behind:

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
       HighLightRow(e, "#86CCF7", "#C0E2F7","Black","Black");
}

Currently rated 4.5 by 4 people

  • Currently 4.5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by Miron on Wednesday, October 17, 2007 10:31 AM
Permalink | Comments (3) | Post RSSRSS comment feed

New & Shiny WebResource.axd compression Module

This project is now an Open source project on  CodePlex web site. Latest code and updates can be found at  http://www.codeplex.com/MbCompression

Background

Running after the best performance for your web application, one of the biggest improvement you can do is to use compression. Page, Stylesheet & Javascript files (images are already compressed format). There are few compression modules out there, all you needs to do is just 'goolge' for it, and the implementation is really very simple. Not a lot of changes in your code are needed.

ASP.NET bring us a new way to integrate resources into our site - using WebResource.axd handler. Those are like a 'Virtual containers' for other file that embedded into a DLL file. It can be Css, Javascript, Images... Actually this is an handler that load the resource from the DLL file. 

The Problem

The pages, .css & .js files can be easily compress with one of the modules on the net. The basic technique is to pass your response through 'Compression Filter'.

WebResource.axd, that became very large (specially when using AJAX) are break down for unknown reason when you try to compress them that way. That result is you will not compress them, and your client will download big files that contains js or css (sometimes of 70kb and even more)

The Challenge

I couldn't find on the net a compression module that compress this type of file. I decided I will try to do it by myself.

First Try.  Working, but bad performance. ( Compress pages and WebResource.axd files in ASP.NET )

I started to make tests and to play with WebResources files. Only WebResources that contains 'text' content (css, js, text) can be consider as compressible. Images are not.

I was thinking, maybe I can load the WebResource, get it's content as string, compress the string and send it to the response as string (or as byte[]). After some tests, It discovered as true. The WebResource was passing to the client completely and without any missing characters. The problem was to get the WebResource content. For that, I had to make a 'fake' request based on the url, asking again for the WebResource and then compress it.

HttpWebResponse request = (HttpWebRequest)WebRequest.Create(app.Context.Request.Url.OriginalString + "&p=1");
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
Stream responseStream = response.GetResponseStream();

That make every time the client ask for webresource, the server made double HTTP request - what a waste.

This module was working. It was actually compressing the WebResources, But suffer from bad performance.

Second Try. Working better. But not enough. ( Compress your pages, css, js and WebResources.axd files for better performance )

Thanks to Mads Kristensen that point me to the performance issue and offer an idea how to improve it, The second try was better. The first time the WebResource was loading, it was compressed and inserted into the Application object, and next time it was requested, it was served from there directly.

It was actually running on our production servers (Where I work) for a while, and compressed the WebResources. But I was not happy with that solution. The 'fake' request throw an exception once in a while. Maybe one for some thousands of users. Also it was not stable enough on some other servers, and throw 404 errors.

Third Try.  Success! Working as expected. No errors and really WebResource compression module!

The compression part in the first two tries was perfect. compress the content of the webresource as byte[] and serve it to the client. The only problem is to load the actual resource content.

In this time, I decided to 'attack' the problem from another way. .NET using System.Web.Handlers.AssemblyResourceLoader to load and serve WebResource requests. I decided to implement my own AssemblyResourceLoader handler that will support compression. unfortunately,  AssemblyResourceLoader handler is registered into the system, and can't be remove. Any try to do so will lead you to an exception.

So, what I did was to create an HttpModule, using the event PreRequestHandlerExecute that is one step before the handle action, do my stuff here, and then end response and jump over the hander step. That way I can prevented AssemblyResourceLoader handler to do his stuff.

To create my version for the AssemblyResourceLoader, I used the source code of 'ASP.NET 2.0 AJAX Extensions' and a reflector to see the code of the original AssemblyResourceLoader.

There are few steps in that module:

1.  Parsing the request query string. The request for WebResource has an encrypted query string with the the resource data separate with pipes chars ('|'):     Assemby info,  Resource name and a char for the 'assembly type'. The query string is encrypted using the machinekey, and needs to be decrypted using reflection. The problem is, that share hosting servers (such GoDaddy) are not permitting using reflection from within your code. Only from assemblies that are in the GAC. What I had to do is to find a public method from an assembly that in the GAC of every server.

The solution I found (if somebody have a better idea, I will be glad to hear) is creating an empty Membership class, that inherits from MembershipProvider, and implement only one public method that using the 'DecryptPassword' method from the  MembershipProvider. it is also using the machinekey.   (To use that decryption, add the attribute reflectionAlloweded="false" to the CompressorSettings section, and machinekey section to the web.config. See Readme.txt file in the source)

private static MethodInfo _decryptString;
private static readonly Object _getMethodLock = new Object();

internal static string DecryptString(string input)
{
    if (Settings.Instance.ReflectionAlloweded)
    {
        if (_decryptString == null)
        {
            lock (_getMethodLock)
            {
                if (_decryptString == null)
                {
                    _decryptString = typeof(Page).GetMethod("DecryptString", BindingFlags.Static | BindingFlags.NonPublic);
                }
            }
        }
        return (string)_decryptString.Invoke(null, new object[] { input });
    }
    else
    {
        return EmptyMembership.Instance.DecryptString(input);
    }
}

2.  Load the assembly. The char for the 'assembly type' in the query string indicate if the assembly is the System.Web assembly or the one that encrypted in the query.

3.  Load the resource info. Using the resource name from the query, we need to load the resource info - it's content type, if it exist and if it needs to perform substitution calls      (WebResource within another WebResource. If so, we let the original System.Web.Handlers.AssemblyResourceLoader to handle this WebResource). Of course we check in our cache if that resource already asked for, and load the info from the cache.

4.  Setting the HttpCachePolicy headers to cache the request, and adding a ETag for checking in the next time.

5.  Get the resource stream from the assembly.

    Stream resourceStream = assembly.GetManifestResourceStream(resourceName)

6. Compress the resource stream if needed. If the resource is 'compressable' it can be read as a string, so we load it as StreamReader, read it to the end, convert the string into byte[] and compress it. If not (in case of images) we just load the resource, and serve it to the client.

     public static byte[] Compressor(byte[] buffer, string encodingType)
     {
         using (MemoryStream memStream = new MemoryStream())
         {
             Stream compress = null;

             // Choose the compression type and make the compression
             if (String.Equals(encodingType, "gzip", StringComparison.Ordinal))
             {
                 compress = new GZipStream(memStream, CompressionMode.Compress);
             }
             else if (String.Equals(encodingType, "deflate", StringComparison.Ordinal))
             {
                 compress = new DeflateStream(memStream, CompressionMode.Compress);
             }
             else
             {
                 // No compression
                 return buffer;
             }

             compress.Write(buffer, 0, buffer.Length);
             compress.Dispose();

             return memStream.ToArray();
         }
     }

7.  Last thing to do, is to send the byte[] (compressed or not to the response stream, and end the response.

      HttpContext.Response.OutputStream.Write(compressedData, 0, compressedData.Length);

Optional Improvements for the WebResource module (maybe in the feature) 

  • Storing the compressed resource in the HttpContext.Cache
  • Compress using the preferred compression algorithm by the client (parsing the 'Accept-encoding' header)
  • Remove white spaces & comments from css & Javascript resources

Tips when working with WebResources

  • Add to your robots.txt file the following:
    User-agent: *
    Disallow: /*.axd$
  • To prevent the search engines to cache the axd handlers. (not 100% solution, but not harmful Cool)

  • Add a machinekey to your Web.Config. ASP.NET use it for validation and encryption/decryption of the view state & the WebResource query strings. Don't relay on the auto generate key. It can lead you to some Cryptographic Exceptions. Use one of the on-line tools to generate key for you. 

Implementation

Full instruction can be found in the readme.txt file in the source code.

The Source Code

The source code contain handlers to compress .js & .css files, simple module to compress pages, the module to compress the WebResources and helper classes. Every module/handler is 'stand alone' and can be use independent in case you choose to use your own implementation for the pages of css/js.

The Readme.txt file include all needed info to implement the handlers/modules.

Important: If you are using the WebResource compression module on share hosting thatnow allowed using reflection from your code (as GoDaddy), you must  add the attribute reflectionAlloweded="false" to the CompressorSettings section.

Latest code can be downloaded from: http://www.codeplex.com/MbCompression

See also post:  Update to my compression module to compress third party scripts 

Currently rated 4.5 by 15 people

  • Currently 4.466668/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Posted by Miron on Saturday, October 13, 2007 5:59 AM
Permalink | Comments (137) | Post RSSRSS comment feed