123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. using ReactNative.Bridge;
  2. using ReactNative.UIManager;
  3. using System;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Runtime.InteropServices.WindowsRuntime;
  7. using System.Threading.Tasks;
  8. using Windows.Graphics.Display;
  9. using Windows.Graphics.Imaging;
  10. using Windows.Storage;
  11. using Windows.Storage.Streams;
  12. using Windows.UI.Xaml;
  13. using Windows.UI.Xaml.Media.Imaging;
  14. namespace RNViewShot
  15. {
  16. public class ViewShot : IUIBlock
  17. {
  18. public const string ErrorUnableToSnapshot = "E_UNABLE_TO_SNAPSHOT";
  19. private int tag;
  20. private string extension;
  21. private double quality;
  22. private int? width;
  23. private int? height;
  24. private string path;
  25. private string result;
  26. private IPromise promise;
  27. public ViewShot(
  28. int tag,
  29. string extension,
  30. double quality,
  31. int? width,
  32. int? height,
  33. string path,
  34. string result,
  35. IPromise promise)
  36. {
  37. this.tag = tag;
  38. this.extension = extension;
  39. this.quality = quality;
  40. this.width = width;
  41. this.height = height;
  42. this.path = path;
  43. this.result = result;
  44. this.promise = promise;
  45. }
  46. public async void Execute(NativeViewHierarchyManager nvhm)
  47. {
  48. var view = nvhm.ResolveView(tag) as FrameworkElement;
  49. if (view == null)
  50. {
  51. promise.Reject(ErrorUnableToSnapshot, "No view found with reactTag: " + tag);
  52. return;
  53. }
  54. try
  55. {
  56. if ("file" == result)
  57. {
  58. using (var ras = new InMemoryRandomAccessStream())
  59. {
  60. await CaptureView(view, ras);
  61. var file = await GetStorageFile();
  62. using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
  63. {
  64. await RandomAccessStream.CopyAndCloseAsync(ras.GetInputStreamAt(0), fileStream.GetOutputStreamAt(0));
  65. promise.Resolve(file.Path);
  66. }
  67. }
  68. }
  69. else if ("base64" == result)
  70. {
  71. using (var ras = new InMemoryRandomAccessStream())
  72. {
  73. await CaptureView(view, ras);
  74. var imageBytes = new byte[ras.Size];
  75. await ras.AsStream().ReadAsync(imageBytes, 0, imageBytes.Length);
  76. string data = Convert.ToBase64String(imageBytes);
  77. promise.Resolve(data);
  78. }
  79. }
  80. else if ("data-uri" == result)
  81. {
  82. using (var ras = new InMemoryRandomAccessStream())
  83. {
  84. await CaptureView(view, ras);
  85. var imageBytes = new byte[ras.Size];
  86. await ras.AsStream().ReadAsync(imageBytes, 0, imageBytes.Length);
  87. string data = Convert.ToBase64String(imageBytes);
  88. data = "data:image/" + extension + ";base64," + data;
  89. promise.Resolve(data);
  90. }
  91. }
  92. else
  93. {
  94. promise.Reject(ErrorUnableToSnapshot, "Unsupported result: " + result + ". Try one of: file | base64 | data-uri");
  95. }
  96. }
  97. catch (Exception ex)
  98. {
  99. Debug.WriteLine(ex.ToString());
  100. promise.Reject(ErrorUnableToSnapshot, "Failed to capture view snapshot");
  101. }
  102. }
  103. private async Task<BitmapEncoder> CaptureView(FrameworkElement view, IRandomAccessStream stream)
  104. {
  105. int w = (int)view.ActualWidth;
  106. int h = (int)view.ActualHeight;
  107. if (w <= 0 || h <= 0)
  108. {
  109. throw new InvalidOperationException("Impossible to snapshot the view: view is invalid");
  110. }
  111. RenderTargetBitmap targetBitmap = new RenderTargetBitmap();
  112. await targetBitmap.RenderAsync(view, w, h);
  113. BitmapEncoder encoder;
  114. if (extension != "png")
  115. {
  116. var propertySet = new BitmapPropertySet();
  117. var qualityValue = new BitmapTypedValue(quality, Windows.Foundation.PropertyType.Single);
  118. propertySet.Add("ImageQuality", qualityValue);
  119. encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream, propertySet);
  120. }
  121. else
  122. {
  123. encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
  124. }
  125. var displayInformation = DisplayInformation.GetForCurrentView();
  126. var pixelBuffer = await targetBitmap.GetPixelsAsync();
  127. encoder.SetPixelData(
  128. BitmapPixelFormat.Bgra8,
  129. BitmapAlphaMode.Ignore,
  130. (uint)targetBitmap.PixelWidth,
  131. (uint)targetBitmap.PixelHeight,
  132. displayInformation.LogicalDpi,
  133. displayInformation.LogicalDpi,
  134. pixelBuffer.ToArray());
  135. if (width != null && height != null && (width != w || height != h))
  136. {
  137. encoder.BitmapTransform.ScaledWidth = (uint)width;
  138. encoder.BitmapTransform.ScaledWidth = (uint)height;
  139. }
  140. if (encoder == null)
  141. {
  142. throw new InvalidOperationException("Impossible to snapshot the view");
  143. }
  144. await encoder.FlushAsync();
  145. return encoder;
  146. }
  147. private async Task<StorageFile> GetStorageFile()
  148. {
  149. var storageFolder = ApplicationData.Current.LocalFolder;
  150. var fileName = string.IsNullOrEmpty(path) ? path : Path.ChangeExtension(Guid.NewGuid().ToString(), extension);
  151. return await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);
  152. }
  153. }
  154. }