Feeds:
Posts
Comments

Archive for January, 2010

I’m always looking for ways to take advantage of Silverlight to accomplish tasks that traditional web technologies can’t. Here’s one that will save you money, increase scalability, and improve your user’s experience

Allowing users to upload images is becoming common place for many websites and applications today. Equally common is the fact that most cameras and phones today produce very large and high quality images. On the other hand, very rarely do you actually want a high resolution / low compression image sitting on your server; nor does the average user know or care enough to manually reduce the size of their image before uploading it to your site.

Therefore, unless you’re lazy or in “get it done” mode, you are probably going to want to shrink the image and even increase the compression on that image before storing it on your server. Doing so will result in faster downloads of that image which means lower bandwidth costs and a better user experience.

The typical (and only option for standard Javascript+HTML based sites) is to do the work on the server. But wouldn’t it be great if you could do this work on the client BEFORE sending it up to your server!? Doing so would 1) decrease the time it takes to upload the image in the first place, 2) decrease the amount of work required by your server; thereby increasing scalability of your site, and 3) decrease the bandwidth used by both you and your user; thereby saving you money.

Silverlight has your answer! Here’s how:

Step 1) Use the OpenFileDialog to acquire the stream that contains the bytes of the image.

OpenFileDialog openDialog = new OpenFileDialog();

openDialog.Filter = “JPEG Files (*.jpg;*.jpeg)|*.jpg;*.jpeg”;

if (openDialog.ShowDialog().GetValueOrDefault(false))

{

    using (FileStream stream = openDialog.File.OpenRead())

    {

        // now you have the filestream

    }

}

Step 2) Create a WriteableBitmap from those bytes using a ScaleTransform to shrink the image.

public static WriteableBitmap GetImageSource(Stream stream, double maxWidth, double maxHeight)

{

    BitmapImage bmp = new BitmapImage();

    bmp.SetSource(stream);

 

    Image img = new Image();

    img.Effect = new DropShadowEffect() { ShadowDepth = 0, BlurRadius = 0 };

    img.Source = bmp;

 

    double scaleX = 1;

    double scaleY = 1;

 

    if (bmp.PixelHeight > maxHeight)

        scaleY = maxHeight / bmp.PixelHeight;

    if (bmp.PixelWidth > maxWidth)

        scaleX = maxWidth / bmp.PixelWidth;

 

    // maintain aspect ratio by picking the most severe scale

    double scale = Math.Min(scaleY, scaleX);

 

    return new WriteableBitmap(img, new ScaleTransform() { ScaleX = scale, ScaleY = scale });

}

Note: I’m still investigating the need for setting the Effect property on your image control. While this code does not actually affect the user’s image, it is necessary to get this to work. Maybe a bug in Silverlight 3?

Step 3) Encode the WriteableBitmap object back to a Jpeg using FJCore and set your compression quality.

Stream Source = Encode(wb, 20);

public static Stream Encode(WriteableBitmap bitmap, int quality)

{

    //Convert the Image to pass into FJCore

    int width = bitmap.PixelWidth;

    int height = bitmap.PixelHeight;

    int bands = 3;

 

    byte[][,] raster = new byte[bands][,];

 

    for (int i = 0; i < bands; i++)

    {

        raster[i] = new byte[width, height];

    }

 

    for (int row = 0; row < height; row++)

    {

        for (int column = 0; column < width; column++)

        {

            int pixel = bitmap.Pixels[width * row + column];

            raster[0][column, row] = (byte)(pixel >> 16);

            raster[1][column, row] = (byte)(pixel >> 8);

            raster[2][column, row] = (byte)pixel;

        }

    }

 

    ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };

 

    FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

 

    //Encode the Image as a JPEG

    MemoryStream stream = new MemoryStream();

    JpegEncoder encoder = new JpegEncoder(img, quality, stream);

 

    encoder.Encode();

 

    //Move back to the start of the stream

    stream.Flush();

    stream.Seek(0, SeekOrigin.Begin);

    return stream;

}

Step 4) Send the stream for the new, potentially MUCH smaller Jpeg up to your server!

byte[] buffer;

using (Stream Source = JpgEncoder.Encode(wb, 50))

{

    int bufferSize = Convert.ToInt32(Source.Length);

    buffer = new byte[bufferSize];

    Source.Read(buffer, 0, bufferSize);

    Source.Close();

}

Service1Client service = new Service1Client();

service.SaveImageAsync(buffer);

That’s it! Plug this code into your Silverlight app everywhere you upload images and improve your website!

Download the source code here.

Credits

Kudos to the folks that made FJCore for sharing all their hard work with those of us that would never consider trying to build it ourselves!

Kudos to nokola for finding a way to build a WriteableBitmap object from an image control without touching the VisualTree.

Read Full Post »