Click here to Skip to main content
15,884,176 members
Articles / Desktop Programming / WPF

Deep Zoom for WPF

Rate me:
Please Sign up or sign in to vote.
4.97/5 (54 votes)
24 Nov 2010Ms-PL13 min read 383.1K   9.1K   100  
An implementation of MultiScaleImage (Deep Zoom) for WPF, compatible with Deep Zoom Composer and Zoom.it.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;

namespace DeepZoom
{
    /// <summary>
    /// A spatial items source that is able to find and cache visible image 
    /// tiles in a the screen. Used in conjuction with ZoomableCanvas.
    /// </summary>
    internal class MultiScaleImageSpatialItemsSource :
        IList,
        ZoomableCanvas.ISpatialItemsSource
    {
        private const int CacheCapacity = 300;       // limit cache to 300 tiles
        private readonly Dictionary<string, BitmapSource> _tileCache = new Dictionary<string, BitmapSource>();
        private readonly Queue<string> _cachedTiles = new Queue<string>(CacheCapacity);
        private readonly MultiScaleTileSource _tileSource;
        private CancellationTokenSource _currentCancellationTokenSource = new CancellationTokenSource();
        private static readonly object CacheLock = new object();

        public MultiScaleImageSpatialItemsSource(MultiScaleTileSource tileSource)
        {
            _tileSource = tileSource;
        }

        public void InvalidateSource()
        {
            if (ExtentChanged != null)
                ExtentChanged(this, EventArgs.Empty);
            if (QueryInvalidated != null)
                QueryInvalidated(this, EventArgs.Empty);
        }

        #region ISpatialItemsSource members

        public Rect Extent
        {
            get
            {
                return new Rect(_tileSource.ImageSize);
            }
        }

        public IEnumerable<int> Query(Rect rectangle)
        {
            return _tileSource.VisibleTilesUntilFill(rectangle, CurrentLevel)
                              .Select(t => _tileSource.GetTileIndex(t));
        }

        public event EventHandler ExtentChanged;
        public event EventHandler QueryInvalidated;

        #endregion

        private int _currentLevel;
        public int CurrentLevel
        {
            get { return _currentLevel; }
            set
            {
                if (value == _currentLevel) return;

                // Cancel all download tasks
                _currentCancellationTokenSource.Cancel();
                _currentCancellationTokenSource = new CancellationTokenSource();

                _currentLevel = value;
            }
        }

        public object this[int i]
        {
            get
            {
                var tile = _tileSource.TileFromIndex(i);
                var tileId = tile.ToString();

                if (_tileCache.ContainsKey(tileId))
                    return new VisualTile(tile, _tileSource, _tileCache[tileId]);

                var tileVm = new VisualTile(tile, _tileSource);
                
                var imageSource = _tileSource.GetTileLayers(tile.Level, tile.Column, tile.Row);

                var uri = imageSource as Uri;
                if (uri != null)
                {
                    // Capture closure
                    var token = _currentCancellationTokenSource.Token;
                    Task.Factory
                        .StartNew(() =>
                        {
                            var source = ImageLoader.LoadImage(uri);
                            if (source != null)
                                source = CacheTile(tileId, source);
                            return source;
                        }, token, TaskCreationOptions.None, TaskScheduler.Default)
                        .ContinueWith(t =>
                        {
                            if (t.Result != null)
                            {
                                tileVm.Source = t.Result;
                            }
                        }, TaskContinuationOptions.OnlyOnRanToCompletion);
                }
                else
                {
                    var stream = imageSource as Stream;
                    if (stream != null)
                    {
                        var source = new BitmapImage();
                        source.BeginInit();
                        source.CacheOption = BitmapCacheOption.OnLoad;
                        source.StreamSource = stream;
                        source.EndInit();

                        var src = CacheTile(tileId, source);
                        tileVm.Source = src;
                    }
                    else return null;
                }

                return tileVm;
            }
            set { }
        }

        private BitmapSource CacheTile(string tileId, BitmapSource source)
        {
            if (_tileCache.ContainsKey(tileId))
                return _tileCache[tileId];

            lock (CacheLock)
            {
                if (_cachedTiles.Count >= CacheCapacity)
                {
                    _tileCache.Remove(_cachedTiles.Dequeue());
                }
                _cachedTiles.Enqueue(tileId);
                _tileCache.Add(tileId, source);
            }
            return source;
        }

        #region Irrelevant IList Members

        int IList.Add(object value)
        {
            return 0;
        }

        void IList.Clear()
        {
        }

        bool IList.Contains(object value)
        {
            return false;
        }

        int IList.IndexOf(object value)
        {
            return 0;
        }

        void IList.Insert(int index, object value)
        {
        }

        void IList.Remove(object value)
        {
        }

        void IList.RemoveAt(int index)
        {
        }

        void ICollection.CopyTo(Array array, int index)
        {
        }

        bool IList.IsFixedSize
        {
            get { return false; }
        }

        bool IList.IsReadOnly
        {
            get { return true; }
        }

        bool ICollection.IsSynchronized
        {
            get { return false; }
        }

        object ICollection.SyncRoot
        {
            get { return null; }
        }

        int ICollection.Count
        {
            get { return int.MaxValue; }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            yield break;
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Virtual Dreams
Brazil Brazil
Hi! I'm Roberto. I'm a Brazilian Engineering student at the University of São Paulo and the Ecole Centrale de Lille (France).

I've participated in the Imagine Cup competition and went to the world finals every year from 2005 to 2009. I also won the 1st place award in 2006, in India, for the Interface Design invitational, in 2007 in Korea, for the Embedded Development invitational, and in 2009 in Egypt for the Windows Mobile Award.

Currently I keep a blog (in English and Portuguese) at http://virtualdreams.com.br/blog/ and a weekly webcast about WPF and Silverlight (in Portuguese) at http://www.xamlcast.net.

Comments and Discussions