ImageToolsiOS.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. using System;
  2. using System.Drawing;
  3. using System.IO;
  4. using System.Threading.Tasks;
  5. using AVFoundation;
  6. using CoreGraphics;
  7. using CoreMedia;
  8. using Foundation;
  9. using UIKit;
  10. using Xamarin.Essentials;
  11. using MobileCoreServices;
  12. [assembly: Xamarin.Forms.Dependency(typeof(InABox.Mobile.iOS.ImageToolsiOS))]
  13. namespace InABox.Mobile.iOS
  14. {
  15. public class ImageToolsiOS : IImageTools
  16. {
  17. public byte[] CreateVideoThumbnail(byte[] video, int maxwidth, int maxheight)
  18. {
  19. byte[] result = null;
  20. var filename = Path.Combine(
  21. Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
  22. $"{Guid.NewGuid().ToString()}.tmp"
  23. );
  24. File.WriteAllBytes(filename,video);
  25. // perhaps? on IOS, try to use - NSUrl.FromFilename (path) instead of - new Foundation.NSUrl(url)
  26. var asset = AVAsset.FromUrl(NSUrl.FromFilename(filename));
  27. AVAssetImageGenerator imageGenerator = new AVAssetImageGenerator(asset);
  28. //AVAssetImageGenerator imageGenerator = new AVAssetImageGenerator(AVAsset.FromUrl((new Foundation.NSUrl(filename))));
  29. imageGenerator.AppliesPreferredTrackTransform = true;
  30. CMTime actualTime;
  31. NSError error;
  32. CGImage cgImage = imageGenerator.CopyCGImageAtTime(new CMTime(1, 1), out actualTime, out error);
  33. using (var ms = new MemoryStream())
  34. {
  35. var png = new UIImage(cgImage).AsPNG().AsStream();
  36. png.CopyTo(ms);
  37. result = ms.ToArray();
  38. }
  39. if (result != null)
  40. result = ScaleImage(result, new Size(maxwidth, maxheight), 60);
  41. File.Delete(filename);
  42. return result;
  43. }
  44. public byte[] CreateThumbnail(byte[] source, int maxwidth, int maxheight)
  45. {
  46. return ScaleImage(source, new Size(maxwidth, maxheight), 60);
  47. }
  48. public async Task<FileResult> PickPhotoAsync(int? compression, Size? constraints)
  49. {
  50. return await MainThread.InvokeOnMainThreadAsync(async () =>
  51. await InternalGetPhotoAsync<Permissions.Photos>(UIImagePickerControllerSourceType.PhotoLibrary, compression, constraints));
  52. }
  53. public async Task<FileResult> CapturePhotoAsync(int? compression, Size? constraints)
  54. {
  55. return await MainThread.InvokeOnMainThreadAsync(async () =>
  56. await InternalGetPhotoAsync<Permissions.Camera>(UIImagePickerControllerSourceType.Camera, compression, constraints));
  57. }
  58. private async Task<FileResult> InternalGetPhotoAsync<TPermission>(UIImagePickerControllerSourceType source, int? compression, Size? constraints)
  59. where TPermission : Permissions.BasePermission, new()
  60. {
  61. var taskCompletionSource = new TaskCompletionSource<FileResult>();
  62. if (await Permissions.RequestAsync<TPermission>() == PermissionStatus.Granted)
  63. {
  64. var imagePicker = new UIImagePickerController
  65. {
  66. SourceType = source,
  67. MediaTypes = new string[] { UTType.Image }
  68. };
  69. var viewController = Platform.GetCurrentUIViewController();
  70. imagePicker.AllowsEditing = false;
  71. imagePicker.FinishedPickingMedia += async (sender, e) =>
  72. {
  73. var jpegFilename = Path.Combine(FileSystem.CacheDirectory, $"{Guid.NewGuid()}.jpg");
  74. var source = e.Info[UIImagePickerController.OriginalImage] as UIImage;
  75. var rotated = AutoRotateImage(source);
  76. var scaled = ScaleImage(rotated, constraints);
  77. var result = scaled.AsJPEG(new nfloat(compression ?? 100)/100);
  78. await viewController.DismissViewControllerAsync(true);
  79. if (result.Save(jpegFilename, false, out var error))
  80. {
  81. taskCompletionSource.TrySetResult(new FileResult(jpegFilename));
  82. }
  83. else
  84. {
  85. taskCompletionSource.TrySetException(new Exception($"Error saving the image: {error}"));
  86. }
  87. imagePicker?.Dispose();
  88. imagePicker = null;
  89. };
  90. imagePicker.Canceled += async (sender, e) =>
  91. {
  92. await viewController.DismissViewControllerAsync(true);
  93. taskCompletionSource.TrySetResult(null);
  94. imagePicker?.Dispose();
  95. imagePicker = null;
  96. };
  97. await viewController.PresentViewControllerAsync(imagePicker, true);
  98. }
  99. else
  100. {
  101. taskCompletionSource.TrySetResult(null);
  102. taskCompletionSource.TrySetException(new PermissionException("Camera permission not granted"));
  103. }
  104. return await taskCompletionSource.Task;
  105. }
  106. private UIImage AutoRotateImage(UIImage source)
  107. {
  108. var rotation = source.Orientation switch
  109. {
  110. UIImageOrientation.Right => 90F,
  111. UIImageOrientation.Up => 0F,
  112. UIImageOrientation.Left => -90F,
  113. UIImageOrientation.Down => 180F,
  114. _ => 0F
  115. };
  116. return RotateImage(source, rotation);
  117. }
  118. private UIImage RotateImage(UIImage source, float rotation)
  119. {
  120. UIImage imageToReturn = null;
  121. float radians = -1 * ((float)(rotation * Math.PI) / 180);
  122. bool isLandscape = false;
  123. var x = source.Size.Width / 2;
  124. var y = source.Size.Height / 2;
  125. //https://stackoverflow.com/a/8536553
  126. CGAffineTransform transform = new CGAffineTransform((nfloat)Math.Cos(radians), (nfloat)Math.Sin(radians), -(nfloat)Math.Sin(radians), (nfloat)Math.Cos(radians), (nfloat)(x - x * Math.Cos(radians)) + (nfloat)(y * Math.Sin(radians)), (nfloat)(y - x * Math.Sin(radians) - y * Math.Cos(radians)));
  127. var diff = (source.Size.Height - source.Size.Width) / 2;
  128. bool translateWidthAndHeight = false;
  129. if (rotation == 90)
  130. {
  131. translateWidthAndHeight = true;
  132. transform.Translate(diff, -diff);
  133. }
  134. else if (rotation == 180)
  135. {
  136. //Transform.Translate(image.Size.Width, -image.Size.Height);
  137. }
  138. else if (rotation == 270)
  139. {
  140. translateWidthAndHeight = true;
  141. transform.Translate(diff, -diff);
  142. }
  143. else if (rotation == 360)
  144. {
  145. }
  146. if (translateWidthAndHeight)
  147. {
  148. //now draw image
  149. using (var context = new CGBitmapContext(IntPtr.Zero,
  150. (int)source.Size.Height,
  151. (int)source.Size.Width,
  152. source.CGImage.BitsPerComponent,
  153. source.CGImage.BitsPerComponent * (int)source.Size.Width,
  154. source.CGImage.ColorSpace,
  155. source.CGImage.BitmapInfo))
  156. {
  157. context.ConcatCTM(transform);
  158. context.DrawImage(new RectangleF(PointF.Empty, new SizeF((float)source.Size.Width, (float)source.Size.Height)), source.CGImage);
  159. using (var imageRef = context.ToImage())
  160. {
  161. imageToReturn = new UIImage(imageRef);
  162. }
  163. }
  164. }
  165. else
  166. {
  167. //now draw image
  168. using (var context = new CGBitmapContext(IntPtr.Zero,
  169. (int)source.Size.Width,
  170. (int)source.Size.Height,
  171. source.CGImage.BitsPerComponent,
  172. source.CGImage.BitsPerComponent * (int)source.Size.Height,
  173. source.CGImage.ColorSpace,
  174. source.CGImage.BitmapInfo))
  175. {
  176. context.ConcatCTM(transform);
  177. context.DrawImage(new RectangleF(PointF.Empty, new SizeF((float)source.Size.Width, (float)source.Size.Height)), source.CGImage);
  178. using (var imageRef = context.ToImage())
  179. {
  180. imageToReturn = new UIImage(imageRef);
  181. }
  182. }
  183. }
  184. return imageToReturn;
  185. }
  186. private UIImage ScaleImage(UIImage sourceImage, Size? constraints)
  187. {
  188. var maxwidth = constraints?.Width ?? sourceImage.Size.Width;
  189. var maxheight = constraints?.Height ?? sourceImage.Size.Height;
  190. var wRatio = maxwidth < sourceImage.Size.Width
  191. ? maxwidth / (double)sourceImage.Size.Width
  192. : 1.0F;
  193. var hRatio = maxheight < sourceImage.Size.Height
  194. ? maxheight / (double)sourceImage.Size.Height
  195. : 1.0F;
  196. var ratio = Math.Min(hRatio, wRatio);
  197. if (ratio < 1.0F)
  198. {
  199. var width = ratio * sourceImage.Size.Width;
  200. var height = ratio * sourceImage.Size.Height;
  201. UIGraphics.BeginImageContext(new CGSize(width, height));
  202. sourceImage.Draw(new CGRect(0, 0, width, height));
  203. var resultImage = UIGraphics.GetImageFromCurrentImageContext();
  204. UIGraphics.EndImageContext();
  205. return resultImage;
  206. }
  207. return sourceImage;
  208. }
  209. public byte[] RotateImage(byte[] source, float angle, int quality = 100)
  210. {
  211. byte[] result = { };
  212. using (UIImage src = UIImage.LoadFromData(NSData.FromArray(source)))
  213. {
  214. if (src != null)
  215. {
  216. var scaled = RotateImage(src, angle);
  217. using (NSData imageData = scaled.AsJPEG(new nfloat((float)quality / 100F)))
  218. {
  219. result = new byte[imageData.Length];
  220. System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, result, 0,
  221. Convert.ToInt32(imageData.Length));
  222. }
  223. }
  224. }
  225. return result;
  226. }
  227. public byte[] ScaleImage(byte[] source, Size? constraints, int quality = 100)
  228. {
  229. byte[] result = { };
  230. using (UIImage src = UIImage.LoadFromData(NSData.FromArray(source)))
  231. {
  232. if (src != null)
  233. {
  234. var scaled = ScaleImage(src, constraints);
  235. using (NSData imageData = scaled.AsJPEG(new nfloat((float)quality / 100F)))
  236. {
  237. result = new byte[imageData.Length];
  238. System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, result, 0,
  239. Convert.ToInt32(imageData.Length));
  240. }
  241. }
  242. }
  243. return result;
  244. }
  245. }
  246. }