PageRenderTime 83ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/BaconographyWP8Core/View/ScalingPictureView.xaml.cs

https://github.com/hippiehunter/Baconography
C# | 344 lines | 268 code | 38 blank | 38 comment | 49 complexity | 0210710368006768f40b3bbd4191d247 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Navigation;
  8. using Microsoft.Phone.Controls;
  9. using Microsoft.Phone.Shell;
  10. using System.Windows.Media.Imaging;
  11. using System.Windows.Input;
  12. using System.ComponentModel;
  13. using System.Windows.Media;
  14. using BaconographyWP8.PlatformServices;
  15. using System.Threading.Tasks;
  16. using System.IO;
  17. using BaconographyPortable.ViewModel;
  18. using GalaSoft.MvvmLight;
  19. using GalaSoft.MvvmLight.Messaging;
  20. using BaconographyPortable.Messages;
  21. using Microsoft.Practices.ServiceLocation;
  22. using BaconographyPortable.Services;
  23. namespace BaconographyWP8.View
  24. {
  25. public partial class ScalingPictureView : UserControl
  26. {
  27. const double MaxScale = 10;
  28. double _scale = 1.0;
  29. double _minScale;
  30. double _coercedScale;
  31. double _originalScale;
  32. Size _viewportSize;
  33. bool _pinching;
  34. Point _screenMidpoint;
  35. Point _relativeMidpoint;
  36. BitmapImage _bitmap;
  37. public static readonly DependencyProperty ImageSourceProperty =
  38. DependencyProperty.Register(
  39. "ImageSource",
  40. typeof(object),
  41. typeof(ScalingPictureView),
  42. new PropertyMetadata(null, OnImageSourceChanged)
  43. );
  44. private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  45. {
  46. var image = (ScalingPictureView)d;
  47. image.ImageSource = e.NewValue;
  48. }
  49. public static readonly DependencyProperty ImageUrlProperty =
  50. DependencyProperty.Register(
  51. "ImageUrl",
  52. typeof(string),
  53. typeof(ScalingPictureView),
  54. new PropertyMetadata(null, OnImageUrlChanged)
  55. );
  56. private static void OnImageUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  57. {
  58. var image = (ScalingPictureView)d;
  59. image.ImageUrl = (string)e.NewValue;
  60. }
  61. public string ImageUrl
  62. {
  63. get;
  64. set;
  65. }
  66. object _imageSource;
  67. public object ImageSource
  68. {
  69. get { return GetValue(ImageSourceProperty); }
  70. set
  71. {
  72. if (_imageSource != value)
  73. {
  74. if (value == null)
  75. {
  76. if (_bitmap != null)
  77. {
  78. _bitmap.ImageOpened -= OnImageOpened;
  79. _bitmap.ImageFailed -= _bitmap_ImageFailed;
  80. _bitmap.UriSource = null;
  81. }
  82. _bitmap = null;
  83. image.Source = null;
  84. }
  85. else if (value is string)
  86. {
  87. _bitmap = new BitmapImage();
  88. _bitmap.CreateOptions = BitmapCreateOptions.None;
  89. _bitmap.ImageOpened += OnImageOpened;
  90. _bitmap.ImageFailed += _bitmap_ImageFailed;
  91. Messenger.Default.Send<LoadingMessage>(new LoadingMessage { Loading = true });
  92. _bitmap.UriSource = new Uri(value as string);
  93. }
  94. else if (value is byte[])
  95. {
  96. _bitmap = new BitmapImage();
  97. _bitmap.CreateOptions = BitmapCreateOptions.None;
  98. _bitmap.ImageOpened += OnImageOpened;
  99. _bitmap.ImageFailed += _bitmap_ImageFailed;
  100. Messenger.Default.Send<LoadingMessage>(new LoadingMessage { Loading = true });
  101. try
  102. {
  103. _bitmap.SetSource(new MemoryStream(value as byte[]));
  104. }
  105. catch
  106. {
  107. // Image load failed. Is this really an image?
  108. ServiceLocator.Current.GetInstance<INavigationService>().NavigateToExternalUri(new Uri(ImageUrl));
  109. _bitmap = null;
  110. _imageSource = null;
  111. }
  112. if (_loaded)
  113. OnImageOpened(null, null);
  114. }
  115. _imageSource = value;
  116. SetValue(ImageSourceProperty, value);
  117. }
  118. }
  119. }
  120. void _bitmap_ImageFailed(object sender, ExceptionRoutedEventArgs e)
  121. {
  122. Messenger.Default.Send<LoadingMessage>(new LoadingMessage { Loading = false });
  123. ServiceLocator.Current.GetInstance<INotificationService>().CreateNotification("image failed to load: " + e.ErrorException);
  124. }
  125. /// <summary>
  126. /// This is a very simple page. We simply bind to the CurrentPicture property on the AlbumsViewModel
  127. /// </summary>
  128. public ScalingPictureView()
  129. {
  130. InitializeComponent();
  131. Loaded += ScalingPictureView_Loaded;
  132. }
  133. bool _loaded = false;
  134. void ScalingPictureView_Loaded(object sender, RoutedEventArgs e)
  135. {
  136. _loaded = true;
  137. if (_bitmap != null)
  138. {
  139. var result = CoerceScaleImpl(viewport.ActualWidth, viewport.ActualHeight, _bitmap.PixelWidth, _bitmap.PixelHeight, 0.0);
  140. _scale = _coercedScale = _minScale = result.Item1;
  141. ResizeImage(true);
  142. image.Source = _bitmap;
  143. Messenger.Default.Send<LoadingMessage>(new LoadingMessage { Loading = false });
  144. }
  145. }
  146. private bool _initialLoad = true;
  147. /// <summary>
  148. /// Either the user has manipulated the image or the size of the viewport has changed. We only
  149. /// care about the size.
  150. /// </summary>
  151. void viewport_ViewportChanged(object sender, System.Windows.Controls.Primitives.ViewportChangedEventArgs e)
  152. {
  153. Size newSize = new Size(viewport.Viewport.Width, viewport.Viewport.Height);
  154. if (newSize != _viewportSize)
  155. {
  156. _viewportSize = newSize;
  157. if (!_initialLoad)
  158. {
  159. CoerceScale(true);
  160. ResizeImage(false);
  161. }
  162. else
  163. _initialLoad = false;
  164. }
  165. }
  166. /// <summary>
  167. /// Handler for the ManipulationStarted event. Set initial state in case
  168. /// it becomes a pinch later.
  169. /// </summary>
  170. void OnManipulationStarted(object sender, ManipulationStartedEventArgs e)
  171. {
  172. _pinching = false;
  173. _originalScale = _scale;
  174. }
  175. /// <summary>
  176. /// Handler for the ManipulationDelta event. It may or may not be a pinch. If it is not a
  177. /// pinch, the ViewportControl will take care of it.
  178. /// </summary>
  179. /// <param name="sender"></param>
  180. /// <param name="e"></param>
  181. void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
  182. {
  183. if (e.PinchManipulation != null && image != null)
  184. {
  185. e.Handled = true;
  186. if (!_pinching)
  187. {
  188. _pinching = true;
  189. Point center = e.PinchManipulation.Original.Center;
  190. _relativeMidpoint = new Point(center.X / image.ActualWidth, center.Y / image.ActualHeight);
  191. var xform = image.TransformToVisual(viewport);
  192. _screenMidpoint = xform.Transform(center);
  193. }
  194. _coercedScale = _scale = _originalScale * e.PinchManipulation.CumulativeScale;
  195. //CoerceScale(false);
  196. ResizeImage(false);
  197. }
  198. else if (_pinching)
  199. {
  200. _pinching = false;
  201. _originalScale = _scale = _coercedScale;
  202. }
  203. }
  204. /// <summary>
  205. /// The manipulation has completed (no touch points anymore) so reset state.
  206. /// </summary>
  207. void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
  208. {
  209. _pinching = false;
  210. _scale = _coercedScale;
  211. }
  212. /// <summary>
  213. /// When a new image is opened, set its initial scale.
  214. /// </summary>
  215. async void OnImageOpened(object sender, RoutedEventArgs e)
  216. {
  217. Messenger.Default.Send<LoadingMessage>(new LoadingMessage { Loading = false });
  218. int yieldCount = 0;
  219. //sanity check just in case the viewport hasnt been put into the visual tree, we need to wait until it is
  220. while(!(viewport != null && viewport.ActualHeight > 100 && viewport.ActualWidth > 100 && _bitmap != null && _bitmap.PixelHeight != 0 && _bitmap.PixelWidth != 0))
  221. {
  222. if (yieldCount++ > 10)
  223. {
  224. return;
  225. }
  226. await Task.Yield();
  227. }
  228. var result = CoerceScaleImpl(viewport.ActualWidth, viewport.ActualHeight, _bitmap.PixelWidth, _bitmap.PixelHeight, 0.0);
  229. _minScale = result.Item1;
  230. _scale = result.Item2;
  231. ResizeImage(true);
  232. image.Source = _bitmap;
  233. }
  234. /// <summary>
  235. /// Adjust the size of the image according to the coerced scale factor. Optionally
  236. /// center the image, otherwise, try to keep the original midpoint of the pinch
  237. /// in the same spot on the screen regardless of the scale.
  238. /// </summary>
  239. /// <param name="center"></param>
  240. void ResizeImage(bool center)
  241. {
  242. if (_coercedScale != 0)
  243. {
  244. double newWidth;
  245. double newHeight;
  246. if (_bitmap != null)
  247. {
  248. newWidth = image.Width = Math.Round(_bitmap.PixelWidth * _coercedScale);
  249. newHeight = image.Height = Math.Round(_bitmap.PixelHeight * _coercedScale);
  250. }
  251. else return;
  252. viewport.Bounds = new Rect(0, 0, newWidth, newHeight);
  253. if (center)
  254. {
  255. viewport.SetViewportOrigin(
  256. new Point(
  257. Math.Round(newWidth / 2),
  258. Math.Round(newHeight / 2)
  259. ));
  260. }
  261. else
  262. {
  263. Point newImgMid = new Point(newWidth * _relativeMidpoint.X, newHeight * _relativeMidpoint.Y);
  264. Point origin = new Point(newImgMid.X - _screenMidpoint.X, newImgMid.Y - _screenMidpoint.Y);
  265. viewport.SetViewportOrigin(origin);
  266. }
  267. }
  268. }
  269. /// <summary>
  270. /// Coerce the scale into being within the proper range. Optionally compute the constraints
  271. /// on the scale so that it will always fill the entire screen and will never get too big
  272. /// to be contained in a hardware surface.
  273. /// </summary>
  274. /// <param name="recompute">Will recompute the min max scale if true.</param>
  275. void CoerceScale(bool recompute)
  276. {
  277. if (viewport != null && _bitmap != null && _bitmap.PixelHeight != 0 && _bitmap.PixelWidth != 0)
  278. {
  279. var result = CoerceScaleImpl(viewport.ActualWidth, viewport.ActualHeight, _bitmap.PixelWidth, _bitmap.PixelHeight, 0.0);
  280. _minScale = result.Item1;
  281. _coercedScale = _scale = result.Item2;
  282. }
  283. }
  284. private static Tuple<double, double> CoerceScaleImpl(double viewWidth, double viewHeight, double bitmapWidth, double bitmapHeight, double scale)
  285. {
  286. double minX = viewWidth / bitmapWidth;
  287. double minY = viewHeight / bitmapHeight;
  288. var minScale = Math.Min(minX, minY);
  289. return Tuple.Create(minScale, Math.Min(MaxScale, Math.Max(scale, minScale)));
  290. }
  291. private void OnDoubleTap(object sender, System.Windows.Input.GestureEventArgs e)
  292. {
  293. var point = e.GetPosition(image);
  294. _relativeMidpoint = new Point(point.X / image.ActualWidth, point.Y / image.ActualHeight);
  295. var xform = image.TransformToVisual(viewport);
  296. _screenMidpoint = xform.Transform(point);
  297. if (_coercedScale >= (_minScale * 2.5) || _coercedScale < 0)
  298. {
  299. _coercedScale = _minScale;
  300. }
  301. else
  302. _coercedScale *= 1.75;
  303. ResizeImage(false);
  304. }
  305. }
  306. }