Loading parts of large images in the Compact Framework

Even at version 3.5, the image manipulation capabilities of the Compact Framework are seriously crappy lacking.  I’ve had all sorts of challenges over the years getting apps to do exactly what I want.  Fortuantely the CF also allows you to do just about anything you can imagine as long as you’re willing to roll up your sleeves and dive into the ugliness of interop.

Today I decided to to to create a simple app that would use large images.  Let’s say you have a modern camera – you know, something made after 1990 that actually generates pictures of a resolution a bit above 240×480.  The challenge with these images is that in order to display them, they have to be uncompressed to a Bitmap, so if you have a picture that is 2048×1536 at a depth of 24 bits per pixel, you’re talking almost 10MB or RAM just to hold that image.  It doesn’t take long before you’re going to have memory pressure issues (in fact you’ll probably have them just trying to load it).

My goal was to display the entire image on my device screen and then allow the user to zoom in on a small area of the image (the zoom is really just a “show this section at the images native resolution” operation).  This simply cannot be done with the System.Drawing namespace.  If you do manage to load the image, you can’t get it to paint both the stretched image as the zoom becasue you simply don’t have the RAM for it.

This is where the SDF’s Imaging namespace comes in.  The Imaging namespace wraps the COM objects in the Imaging library.  It took a little work, but I generated a quick helper class to generate both thumbnails and clips:

public static class ImageHelper
{
    private static ImagingFactory m_factory;
    private static ImagingFactory GetFactory()
    {
        if (m_factory == null)
        {
            m_factory = new ImagingFactory();
        }

        return m_factory;
    }

    public static Bitmap CreateClip(StreamOnFile sof, int x, int y, int width, int height)
    {
        IBitmapImage original = null;
        IImage image = null;
        ImageInfo info;

        GetFactory().CreateImageFromStream(sof, out image);
        try
        {
            image.GetImageInfo(out info);

            GetFactory().CreateBitmapFromImage(image, info.Width, info.Height,
            info.PixelFormat, InterpolationHint.InterpolationHintDefault, out original);

            try
            {
                var ops = (IBasicBitmapOps)original;
                IBitmapImage clip = null;

                try
                {
                    var rect = new RECT(x, y, x + width, y + height);
                    ops.Clone(rect, out clip, true);

                    return ImageUtils.IBitmapImageToBitmap(clip);
                }
                finally
                {
                    Marshal.ReleaseComObject(clip);
                }
            }
            finally
            {
                Marshal.ReleaseComObject(original);
            }
        }
        finally
        {
            Marshal.ReleaseComObject(image);
        }
    }

    public static Bitmap CreateThumbnail(StreamOnFile sof, int width, int height)
    {
        IBitmapImage thumbnail;
        IImage image;
        ImageInfo info;

        GetFactory().CreateImageFromStream(sof, out image);
        try
        {
            image.GetImageInfo(out info);

            GetFactory().CreateBitmapFromImage(image, (uint)width, (uint)height,
            info.PixelFormat, InterpolationHint.InterpolationHintDefault, out thumbnail);
            try
            {
                return ImageUtils.IBitmapImageToBitmap(thumbnail);
            }
            finally
            {
                Marshal.ReleaseComObject(thumbnail);
            }
        }
        finally
        {
            Marshal.ReleaseComObject(image);
        }
    }

    public static Size GetRawImageSize(StreamOnFile sof)
    {
        IImage image;
        ImageInfo info;

        GetFactory().CreateImageFromStream(sof, out image);
        try
        {
            image.GetImageInfo(out info);

            return new Size((int)info.Width, (int)info.Height);
        }
        finally
        {
            Marshal.ReleaseComObject(image);
        }
    }
}

With that, creating the actual application became nearly trivial:

public partial class Form1 : Form
{
    StreamOnFile m_stream;
    Size m_size;
    public Form1()
    {
        InitializeComponent();
        thumbnail.MouseDown += new MouseEventHandler(thumbnail_MouseDown);
    }

    private void load_Click(object sender, EventArgs e)
    {
        var stream = File.Open("\\Program Files\\ThumbnailExample\\bigimage.jpg", FileMode.Open);
        m_stream = new StreamOnFile(stream);

        m_size = ImageHelper.GetRawImageSize(m_stream);
        thumbnail.Image = ImageHelper.CreateThumbnail(m_stream, thumbnail.Width, thumbnail.Height);

        size.Text = string.Format("{0}x{1}", m_size.Width, m_size.Height);
        load.Enabled = false;
    }

    void thumbnail_MouseDown(object sender, MouseEventArgs e)
    {
        // scale
        int x = (e.X * (m_size.Width / thumbnail.Width)) - (clip.Width / 2);
        int y = (e.Y * (m_size.Height / thumbnail.Height)) - (clip.Height / 2);

        if (clip.Image != null) clip.Image.Dispose();

        clip.Image = ImageHelper.CreateClip(m_stream, x, y, clip.Width, clip.Height);
    }
}

And the output looks something like this:

2 Comments

  1. Hi,
    Thanks a lot for this code. The thumbnail code works like a charm, but;
    Is there any way to do the clip operation without using the line of code:

    GetFactory().CreateBitmapFromImage(image, info.Width, info.Height,
    info.PixelFormat, InterpolationHint.InterpolationHintDefault, out original);

    beacuse on big images (10 MB) you will have to get an Out Of Memory Exception by getting the original image.
    I need a way to get a clip of an image without getting the original image first. Is it at all possible?

    • Hi LilleMonster I have been working on this same problem for the last week myself. Did you get any further with this? I thought using OpenNETCF was supposed to allow loading large images? Do you know what the actual limit is?

Leave a Reply