Feeds:
Posts
Comments

Posts Tagged ‘WriteableBitmap’

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.

Advertisements

Read Full Post »

Silverlight 3 introduces the WriteableBitmap class and with it, the ability to crop an image programmatically on the client!

All you need to do is create a new instance of the WritableBitmap class as your destination (supplying the dimensions in the constructor). Then create or acquire another instance of the WriteableBitmap class fully loaded with an image (among other ways, you can do this by creating a BitmapSource object from a stream via .SetSource and passing that BitmapSource instance into the constructor of a new WriteableBitmap)

Once you have your source and destination WriteableBitmap classes, just retrieve one pixel at a time from the source instance and set that pixel on a destination instance. The pixels are stored in a property on the object call Pixels which is a 1 dimensional array. Geting the index of a given pixel in an array is simple: index = x + y * width.

In my first pass, I just looped thru pixel by pixel. This was fast but not as fast as using Array.Copy (almost twice as fast)…

private static WriteableBitmap CropImage(WriteableBitmap Source, int XOffset, int YOffset, int Width, int Height)

{

    int SourceWidth = Source.PixelWidth;

    WriteableBitmap Result = new WriteableBitmap(Width, Height);

    for (int y = 0; y <= Height – 1; y++)

    {

        int SourceIndex = XOffset + (YOffset + y) * SourceWidth;

        int DestIndex = y * Width;

 

        Array.Copy(Source.Pixels, SourceIndex, Result.Pixels, DestIndex, Width);

    }

    return Result;

}

The result (a WriteableBitmap) can then simply be used as the source of an Image control to display your cropped image.

Note: WriteableBitmap.PixelWidth is expensive. Be sure to call it only once if possible.

Possible Improvement: Had the source width and destination width been the same I could have done it in a single call to Array.Copy and presumably made it even faster.

It’s high time that web developers are able to do complicated tasks on the client and not forced to use the server just because the client-side platform doesn’t support it. We’re still not all the way there yet but Silverlight gets us a lot closer than anything before it.

Read Full Post »