2012-11-22 21:42:29 +01:00
|
|
|
|
// XAML Map Control - http://xamlmapcontrol.codeplex.com/
|
2014-01-10 20:11:39 +01:00
|
|
|
|
// Copyright © 2014 Clemens Fischer
|
2012-05-04 12:52:20 +02:00
|
|
|
|
// Licensed under the Microsoft Public License (Ms-PL)
|
|
|
|
|
|
|
|
|
|
|
|
using System;
|
2012-08-06 17:41:39 +02:00
|
|
|
|
using System.Collections.Concurrent;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
using System.Collections.Generic;
|
2012-07-03 18:03:56 +02:00
|
|
|
|
using System.Diagnostics;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Net;
|
2012-06-24 23:42:11 +02:00
|
|
|
|
using System.Runtime.Caching;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
using System.Threading;
|
2012-11-07 17:48:15 +01:00
|
|
|
|
using System.Windows.Media;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
using System.Windows.Media.Imaging;
|
|
|
|
|
|
using System.Windows.Threading;
|
|
|
|
|
|
|
|
|
|
|
|
namespace MapControl
|
|
|
|
|
|
{
|
2012-05-04 12:52:20 +02:00
|
|
|
|
/// <summary>
|
2013-11-12 21:14:53 +01:00
|
|
|
|
/// Loads map tile images and optionally caches them in a System.Runtime.Caching.ObjectCache.
|
2012-05-04 12:52:20 +02:00
|
|
|
|
/// </summary>
|
2012-08-24 18:10:12 +02:00
|
|
|
|
public class TileImageLoader
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-08-15 21:31:10 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Default Name of an ObjectCache instance that is assigned to the Cache property.
|
|
|
|
|
|
/// </summary>
|
2013-11-12 21:14:53 +01:00
|
|
|
|
public const string DefaultCacheName = "TileCache";
|
2012-08-15 21:31:10 +02:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Default value for the directory where an ObjectCache instance may save cached data.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static readonly string DefaultCacheDirectory =
|
|
|
|
|
|
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl");
|
|
|
|
|
|
|
2012-07-03 18:03:56 +02:00
|
|
|
|
/// <summary>
|
2014-07-01 18:57:44 +02:00
|
|
|
|
/// The ObjectCache used to cache tile images. The default is MemoryCache.Default.
|
2012-07-03 18:03:56 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static ObjectCache Cache { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2013-11-12 21:14:53 +01:00
|
|
|
|
/// The time interval after which cached images expire. The default value is seven days.
|
|
|
|
|
|
/// When an image is not retrieved from the cache during this interval it is considered as expired
|
|
|
|
|
|
/// and will be removed from the cache, provided that the cache implementation supports expiration.
|
|
|
|
|
|
/// If an image is retrieved from the cache and the CacheUpdateAge time interval has expired,
|
|
|
|
|
|
/// the image is downloaded again and rewritten to the cache with a new expiration time.
|
2012-07-03 18:03:56 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static TimeSpan CacheExpiration { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// The time interval after which a cached image is updated and rewritten to the cache.
|
2013-11-12 21:14:53 +01:00
|
|
|
|
/// The default value is one day. This time interval should not be greater than the value
|
2012-07-07 17:19:10 +02:00
|
|
|
|
/// of the CacheExpiration property.
|
2014-01-30 20:01:44 +01:00
|
|
|
|
/// If CacheUpdateAge is less than or equal to TimeSpan.Zero, cached images are never updated.
|
2012-07-03 18:03:56 +02:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static TimeSpan CacheUpdateAge { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
static TileImageLoader()
|
2012-06-24 23:42:11 +02:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
Cache = MemoryCache.Default;
|
2013-11-12 21:14:53 +01:00
|
|
|
|
CacheExpiration = TimeSpan.FromDays(7);
|
|
|
|
|
|
CacheUpdateAge = TimeSpan.FromDays(1);
|
2012-06-24 23:42:11 +02:00
|
|
|
|
}
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
2013-11-12 21:14:53 +01:00
|
|
|
|
private readonly ConcurrentQueue<Tile> pendingTiles = new ConcurrentQueue<Tile>();
|
|
|
|
|
|
private int threadCount;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
2013-11-12 21:14:53 +01:00
|
|
|
|
internal void BeginGetTiles(TileLayer tileLayer, IEnumerable<Tile> tiles)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (tiles.Any())
|
2013-08-29 15:49:48 +02:00
|
|
|
|
{
|
2013-11-12 21:14:53 +01:00
|
|
|
|
// get current TileLayer property values in UI thread
|
|
|
|
|
|
var tileSource = tileLayer.TileSource;
|
2014-07-01 18:57:44 +02:00
|
|
|
|
var imageTileSource = tileSource as ImageTileSource;
|
2013-11-12 21:14:53 +01:00
|
|
|
|
var animateOpacity = tileLayer.AnimateTileOpacity;
|
2014-07-01 18:57:44 +02:00
|
|
|
|
var dispatcher = tileLayer.Dispatcher;
|
2013-11-12 21:14:53 +01:00
|
|
|
|
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (imageTileSource != null && !imageTileSource.IsAsync) // call LoadImage in UI thread
|
|
|
|
|
|
{
|
|
|
|
|
|
var setImageAction = new Action<Tile>(t => t.SetImageSource(LoadImage(imageTileSource, t), animateOpacity));
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var tile in tiles)
|
|
|
|
|
|
{
|
|
|
|
|
|
dispatcher.BeginInvoke(setImageAction, DispatcherPriority.Background, tile); // with low priority
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var tileList = tiles.ToList();
|
|
|
|
|
|
var sourceName = tileLayer.SourceName;
|
|
|
|
|
|
var maxDownloads = tileLayer.MaxParallelDownloads;
|
|
|
|
|
|
|
|
|
|
|
|
ThreadPool.QueueUserWorkItem(o =>
|
|
|
|
|
|
GetTiles(tileList, dispatcher, tileSource, sourceName, animateOpacity, maxDownloads));
|
|
|
|
|
|
}
|
2013-08-29 15:49:48 +02:00
|
|
|
|
}
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-07-20 21:57:29 +02:00
|
|
|
|
internal void CancelGetTiles()
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-08-06 17:41:39 +02:00
|
|
|
|
Tile tile;
|
|
|
|
|
|
while (pendingTiles.TryDequeue(out tile)) ; // no Clear method
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-01 18:57:44 +02:00
|
|
|
|
private void GetTiles(List<Tile> tiles, Dispatcher dispatcher, TileSource tileSource, string sourceName, bool animateOpacity, int maxDownloads)
|
2013-01-17 18:48:38 +01:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (Cache != null && !string.IsNullOrWhiteSpace(sourceName) &&
|
|
|
|
|
|
!(tileSource is ImageTileSource) && !tileSource.UriFormat.StartsWith("file:"))
|
2012-07-20 21:57:29 +02:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
var setImageAction = new Action<Tile, ImageSource>((t, i) => t.SetImageSource(i, animateOpacity));
|
|
|
|
|
|
var outdatedTiles = new List<Tile>(tiles.Count);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var tile in tiles)
|
2012-07-03 18:03:56 +02:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
var buffer = Cache.Get(TileCache.Key(sourceName, tile)) as byte[];
|
|
|
|
|
|
var image = CreateImage(buffer);
|
2013-11-21 21:16:29 +01:00
|
|
|
|
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (image == null)
|
2013-11-12 21:14:53 +01:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
pendingTiles.Enqueue(tile); // not yet cached
|
2013-11-12 21:14:53 +01:00
|
|
|
|
}
|
2014-07-01 18:57:44 +02:00
|
|
|
|
else if (CacheUpdateAge > TimeSpan.Zero && TileCache.CreationTime(buffer) + CacheUpdateAge < DateTime.UtcNow)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
dispatcher.Invoke(setImageAction, tile, image); // synchronously before enqueuing
|
|
|
|
|
|
outdatedTiles.Add(tile); // update outdated cache
|
2013-11-12 21:14:53 +01:00
|
|
|
|
}
|
2014-07-01 18:57:44 +02:00
|
|
|
|
else
|
2013-11-12 21:14:53 +01:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
dispatcher.BeginInvoke(setImageAction, tile, image);
|
2012-11-06 19:49:29 +01:00
|
|
|
|
}
|
2013-11-21 21:16:29 +01:00
|
|
|
|
}
|
2014-07-01 18:57:44 +02:00
|
|
|
|
|
|
|
|
|
|
tiles = outdatedTiles; // enqueue outdated tiles after current tiles
|
2013-11-12 21:14:53 +01:00
|
|
|
|
}
|
2012-06-24 23:42:11 +02:00
|
|
|
|
|
2013-11-12 21:14:53 +01:00
|
|
|
|
foreach (var tile in tiles)
|
|
|
|
|
|
{
|
|
|
|
|
|
pendingTiles.Enqueue(tile);
|
|
|
|
|
|
}
|
2012-08-24 18:10:12 +02:00
|
|
|
|
|
2013-11-12 21:14:53 +01:00
|
|
|
|
while (threadCount < Math.Min(pendingTiles.Count, maxDownloads))
|
|
|
|
|
|
{
|
|
|
|
|
|
Interlocked.Increment(ref threadCount);
|
|
|
|
|
|
|
|
|
|
|
|
ThreadPool.QueueUserWorkItem(o => LoadPendingTiles(dispatcher, tileSource, sourceName, animateOpacity));
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-11-12 21:14:53 +01:00
|
|
|
|
private void LoadPendingTiles(Dispatcher dispatcher, TileSource tileSource, string sourceName, bool animateOpacity)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2013-11-21 21:16:29 +01:00
|
|
|
|
var setImageAction = new Action<Tile, ImageSource>((t, i) => t.SetImageSource(i, animateOpacity));
|
2013-11-12 21:14:53 +01:00
|
|
|
|
var imageTileSource = tileSource as ImageTileSource;
|
2012-08-06 17:41:39 +02:00
|
|
|
|
Tile tile;
|
2013-04-04 18:01:13 +02:00
|
|
|
|
|
2012-08-06 17:41:39 +02:00
|
|
|
|
while (pendingTiles.TryDequeue(out tile))
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2013-01-17 18:48:38 +01:00
|
|
|
|
byte[] buffer = null;
|
|
|
|
|
|
ImageSource image = null;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
2013-01-17 18:48:38 +01:00
|
|
|
|
if (imageTileSource != null)
|
2012-07-20 21:57:29 +02:00
|
|
|
|
{
|
2013-11-12 21:14:53 +01:00
|
|
|
|
image = LoadImage(imageTileSource, tile);
|
2013-01-17 18:48:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2013-11-12 21:14:53 +01:00
|
|
|
|
var uri = tileSource.GetUri(tile.XIndex, tile.Y, tile.ZoomLevel);
|
2013-01-17 18:48:38 +01:00
|
|
|
|
|
2013-04-04 18:01:13 +02:00
|
|
|
|
if (uri != null)
|
2013-01-17 18:48:38 +01:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (uri.Scheme == "file") // create from FileStream because creating from URI leaves the file open
|
2013-04-04 18:01:13 +02:00
|
|
|
|
{
|
2013-12-05 17:56:04 +01:00
|
|
|
|
image = CreateImage(uri.LocalPath);
|
2013-04-04 18:01:13 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2013-11-12 21:14:53 +01:00
|
|
|
|
buffer = DownloadImage(uri);
|
|
|
|
|
|
image = CreateImage(buffer);
|
2013-04-04 18:01:13 +02:00
|
|
|
|
}
|
2013-01-17 18:48:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (image != null || !tile.HasImageSource) // set null image if tile does not yet have an ImageSource
|
2013-01-17 18:48:38 +01:00
|
|
|
|
{
|
2014-01-30 20:01:44 +01:00
|
|
|
|
dispatcher.BeginInvoke(setImageAction, tile, image);
|
2013-11-12 21:14:53 +01:00
|
|
|
|
}
|
2013-01-17 18:48:38 +01:00
|
|
|
|
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (image != null && buffer != null && Cache != null && !string.IsNullOrWhiteSpace(sourceName))
|
2013-11-12 21:14:53 +01:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
Cache.Set(TileCache.Key(sourceName, tile), buffer, new CacheItemPolicy { SlidingExpiration = CacheExpiration });
|
2012-07-20 21:57:29 +02:00
|
|
|
|
}
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
2012-08-24 18:10:12 +02:00
|
|
|
|
|
2013-11-12 21:14:53 +01:00
|
|
|
|
Interlocked.Decrement(ref threadCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static ImageSource LoadImage(ImageTileSource tileSource, Tile tile)
|
|
|
|
|
|
{
|
|
|
|
|
|
ImageSource image = null;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
image = tileSource.LoadImage(tile.XIndex, tile.Y, tile.ZoomLevel);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Trace.TraceWarning("Loading tile image failed: {0}", ex.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return image;
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2013-11-12 21:14:53 +01:00
|
|
|
|
private static ImageSource CreateImage(string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
ImageSource image = null;
|
|
|
|
|
|
|
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2013-12-05 17:56:04 +01:00
|
|
|
|
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
|
2013-11-12 21:14:53 +01:00
|
|
|
|
{
|
|
|
|
|
|
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Trace.TraceWarning("Creating tile image failed: {0}", ex.Message);
|
|
|
|
|
|
}
|
2012-08-24 18:10:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2013-01-17 18:48:38 +01:00
|
|
|
|
return image;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-04-04 18:01:13 +02:00
|
|
|
|
private static ImageSource CreateImage(byte[] buffer)
|
2013-01-17 18:48:38 +01:00
|
|
|
|
{
|
2013-11-12 21:14:53 +01:00
|
|
|
|
ImageSource image = null;
|
2013-01-17 18:48:38 +01:00
|
|
|
|
|
2014-07-01 18:57:44 +02:00
|
|
|
|
if (buffer != null)
|
2013-01-17 18:48:38 +01:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
using (var stream = TileCache.ImageStream(buffer))
|
2013-01-17 18:48:38 +01:00
|
|
|
|
{
|
2013-11-12 21:14:53 +01:00
|
|
|
|
image = BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
|
2013-01-17 18:48:38 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Trace.TraceWarning("Creating tile image failed: {0}", ex.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2012-11-22 21:42:29 +01:00
|
|
|
|
|
2013-01-17 18:48:38 +01:00
|
|
|
|
return image;
|
2012-08-24 18:10:12 +02:00
|
|
|
|
}
|
2012-06-24 23:42:11 +02:00
|
|
|
|
|
2012-08-24 18:10:12 +02:00
|
|
|
|
private static byte[] DownloadImage(Uri uri)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] buffer = null;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2012-11-22 21:42:29 +01:00
|
|
|
|
var request = (HttpWebRequest)WebRequest.Create(uri);
|
2013-01-17 18:48:38 +01:00
|
|
|
|
request.UserAgent = "XAML Map Control";
|
2012-04-25 22:02:53 +02:00
|
|
|
|
|
2012-12-06 23:28:12 +01:00
|
|
|
|
using (var response = (HttpWebResponse)request.GetResponse())
|
|
|
|
|
|
using (var responseStream = response.GetResponseStream())
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2014-07-01 18:57:44 +02:00
|
|
|
|
buffer = TileCache.CreateBuffer(responseStream, (int)response.ContentLength);
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2012-07-07 17:19:10 +02:00
|
|
|
|
catch (WebException ex)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-07-07 17:19:10 +02:00
|
|
|
|
if (ex.Status == WebExceptionStatus.ProtocolError)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-11-22 21:42:29 +01:00
|
|
|
|
var statusCode = ((HttpWebResponse)ex.Response).StatusCode;
|
2012-08-24 18:10:12 +02:00
|
|
|
|
if (statusCode != HttpStatusCode.NotFound)
|
|
|
|
|
|
{
|
2013-11-05 18:00:03 +01:00
|
|
|
|
Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message);
|
2012-08-24 18:10:12 +02:00
|
|
|
|
}
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2012-08-24 18:10:12 +02:00
|
|
|
|
Trace.TraceWarning("Downloading {0} failed with {1}: {2}", uri, ex.Status, ex.Message);
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2012-07-07 17:19:10 +02:00
|
|
|
|
catch (Exception ex)
|
2012-04-25 22:02:53 +02:00
|
|
|
|
{
|
2012-08-24 18:10:12 +02:00
|
|
|
|
Trace.TraceWarning("Downloading {0} failed: {1}", uri, ex.Message);
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-07-03 18:03:56 +02:00
|
|
|
|
return buffer;
|
|
|
|
|
|
}
|
2014-07-01 18:57:44 +02:00
|
|
|
|
|
|
|
|
|
|
private static class TileCache
|
|
|
|
|
|
{
|
|
|
|
|
|
private const int imageBufferOffset = sizeof(Int64);
|
|
|
|
|
|
|
|
|
|
|
|
public static string Key(string sourceName, Tile tile)
|
|
|
|
|
|
{
|
|
|
|
|
|
return string.Format("{0}/{1}/{2}/{3}", sourceName, tile.ZoomLevel, tile.XIndex, tile.Y);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static MemoryStream ImageStream(byte[] cacheBuffer)
|
|
|
|
|
|
{
|
|
|
|
|
|
return new MemoryStream(cacheBuffer, imageBufferOffset, cacheBuffer.Length - imageBufferOffset, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static DateTime CreationTime(byte[] cacheBuffer)
|
|
|
|
|
|
{
|
|
|
|
|
|
return DateTime.FromBinary(BitConverter.ToInt64(cacheBuffer, 0));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static byte[] CreateBuffer(Stream imageStream, int length)
|
|
|
|
|
|
{
|
|
|
|
|
|
var creationTime = BitConverter.GetBytes(DateTime.UtcNow.ToBinary());
|
|
|
|
|
|
|
|
|
|
|
|
using (var memoryStream = length > 0 ? new MemoryStream(length + imageBufferOffset) : new MemoryStream())
|
|
|
|
|
|
{
|
|
|
|
|
|
memoryStream.Write(creationTime, 0, imageBufferOffset);
|
|
|
|
|
|
imageStream.CopyTo(memoryStream);
|
|
|
|
|
|
|
|
|
|
|
|
return length > 0 ? memoryStream.GetBuffer() : memoryStream.ToArray();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2012-04-25 22:02:53 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|