ImageTools.iOS.cs 12 KB

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