Methods.cs 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. using Android.Gms.Extensions;
  2. using Android.Graphics;
  3. using Android.Runtime;
  4. using Android.Util;
  5. using AndroidX.Camera.View.Transform;
  6. using InABox.Avalonia.Platform.Barcodes;
  7. using Java.Net;
  8. using Java.Util;
  9. using Microsoft.Maui.Graphics;
  10. using Microsoft.Maui.Storage;
  11. using Xamarin.Google.MLKit.Vision.Barcode.Common;
  12. using Xamarin.Google.MLKit.Vision.BarCode;
  13. using Xamarin.Google.MLKit.Vision.Common;
  14. using Image = Android.Media.Image;
  15. using Paint = Android.Graphics.Paint;
  16. using RectF = Microsoft.Maui.Graphics.RectF;
  17. using Size = Android.Util.Size;
  18. using ARectF = Android.Graphics.RectF;
  19. namespace InABox.Avalonia.Platform.Android.Barcodes;
  20. public static partial class Methods
  21. {
  22. private static readonly bool neonSupported = IsNeonSupported();
  23. private static readonly ParallelOptions parallelOptions = new()
  24. {
  25. MaxDegreeOfParallelism = Environment.ProcessorCount * 2
  26. };
  27. public static async Task<IReadOnlySet<BarcodeResult>> ScanFromImageAsync(byte[] imageArray)
  28. => await ProcessBitmapAsync(await BitmapFactory.DecodeByteArrayAsync(imageArray, 0, imageArray.Length));
  29. public static async Task<IReadOnlySet<BarcodeResult>> ScanFromImageAsync(FileResult file)
  30. => await ProcessBitmapAsync(await BitmapFactory.DecodeStreamAsync(await file.OpenReadAsync()));
  31. public static async Task<IReadOnlySet<BarcodeResult>> ScanFromImageAsync(string url)
  32. => await ProcessBitmapAsync(await BitmapFactory.DecodeStreamAsync(new URL(url).OpenStream()));
  33. public static async Task<IReadOnlySet<BarcodeResult>> ScanFromImageAsync(Stream stream)
  34. => await ProcessBitmapAsync(await BitmapFactory.DecodeStreamAsync(stream));
  35. private static async Task<IReadOnlySet<BarcodeResult>> ProcessBitmapAsync(Bitmap? bitmap)
  36. {
  37. var barcodeResults = new HashSet<BarcodeResult>();
  38. if (bitmap is null)
  39. return barcodeResults;
  40. using var scanner = BarcodeScanning.GetClient(new BarcodeScannerOptions.Builder()
  41. .SetBarcodeFormats(Barcode.FormatAllFormats)
  42. .Build());
  43. using var image = InputImage.FromBitmap(bitmap, 0);
  44. using var results = await scanner.Process(image).AsAsync<Java.Lang.Object>();
  45. ProcessBarcodeResult(results, barcodeResults);
  46. using var invertedBitmap = Bitmap.CreateBitmap(bitmap.Height, bitmap.Width, bitmap.GetConfig());
  47. using var canvas = new Canvas(invertedBitmap);
  48. using var paint = new Paint();
  49. using var matrixInvert = new ColorMatrix();
  50. matrixInvert.Set(
  51. [
  52. -1.0f, 0.0f, 0.0f, 0.0f, 255.0f,
  53. 0.0f, -1.0f, 0.0f, 0.0f, 255.0f,
  54. 0.0f, 0.0f, -1.0f, 0.0f, 255.0f,
  55. 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
  56. ]);
  57. using var filter = new ColorMatrixColorFilter(matrixInvert);
  58. paint.SetColorFilter(filter);
  59. canvas.DrawBitmap(bitmap, 0, 0, paint);
  60. using var invertedImage = InputImage.FromBitmap(invertedBitmap, 0);
  61. using var invertedResults = await scanner.Process(invertedImage).AsAsync<Java.Lang.Object>();
  62. ProcessBarcodeResult(invertedResults, barcodeResults);
  63. return barcodeResults;
  64. }
  65. private static void ProcessBarcodeResult(Java.Lang.Object? inputResults, HashSet<BarcodeResult> outputResults)
  66. {
  67. if (inputResults is not JavaList javaList)
  68. return;
  69. foreach (Barcode barcode in javaList)
  70. {
  71. if (barcode is null)
  72. continue;
  73. if (string.IsNullOrEmpty(barcode.DisplayValue) && string.IsNullOrEmpty(barcode.RawValue))
  74. continue;
  75. outputResults.Add(barcode.AsBarcodeResult());
  76. }
  77. }
  78. // [LibraryImport("libInvertBytes.so")]
  79. // private static partial int InvertBytes(IntPtr data, int length);
  80. internal static void InvertLuminance(Image image)
  81. {
  82. var yBuffer = image.GetPlanes()?[0].Buffer;
  83. if (yBuffer is null)
  84. return;
  85. if (yBuffer.IsDirect)
  86. {
  87. var data = yBuffer.GetDirectBufferAddress();
  88. var length = yBuffer.Capacity();
  89. if (!neonSupported/* || InvertBytes(data, length) != 0*/)
  90. {
  91. unsafe
  92. {
  93. var dataPtr = (ulong*)data;
  94. Parallel.For(0, length >> 3, parallelOptions, (i) => dataPtr[i] = ~dataPtr[i]);
  95. }
  96. }
  97. }
  98. else
  99. {
  100. using var bits = BitSet.ValueOf(yBuffer);
  101. bits?.Flip(0, bits.Length());
  102. yBuffer.Rewind();
  103. yBuffer.Put(bits?.ToByteArray() ?? []);
  104. }
  105. }
  106. internal static BarcodeTypes ConvertBarcodeResultTypes(int barcodeValueType)
  107. {
  108. return barcodeValueType switch
  109. {
  110. Barcode.TypeCalendarEvent => BarcodeTypes.CalendarEvent,
  111. Barcode.TypeContactInfo => BarcodeTypes.ContactInfo,
  112. Barcode.TypeDriverLicense => BarcodeTypes.DriversLicense,
  113. Barcode.TypeEmail => BarcodeTypes.Email,
  114. Barcode.TypeGeo => BarcodeTypes.GeographicCoordinates,
  115. Barcode.TypeIsbn => BarcodeTypes.Isbn,
  116. Barcode.TypePhone => BarcodeTypes.Phone,
  117. Barcode.TypeProduct => BarcodeTypes.Product,
  118. Barcode.TypeSms => BarcodeTypes.Sms,
  119. Barcode.TypeText => BarcodeTypes.Text,
  120. Barcode.TypeUrl => BarcodeTypes.Url,
  121. Barcode.TypeWifi => BarcodeTypes.WiFi,
  122. _ => BarcodeTypes.Unknown
  123. };
  124. }
  125. internal static int ConvertBarcodeFormats(BarcodeFormats barcodeFormats)
  126. {
  127. var formats = Barcode.FormatAllFormats;
  128. if (barcodeFormats.HasFlag(BarcodeFormats.Code128))
  129. formats |= Barcode.FormatCode128;
  130. if (barcodeFormats.HasFlag(BarcodeFormats.Code39))
  131. formats |= Barcode.FormatCode39;
  132. if (barcodeFormats.HasFlag(BarcodeFormats.Code93))
  133. formats |= Barcode.FormatCode93;
  134. if (barcodeFormats.HasFlag(BarcodeFormats.CodaBar))
  135. formats |= Barcode.FormatCodabar;
  136. if (barcodeFormats.HasFlag(BarcodeFormats.DataMatrix))
  137. formats |= Barcode.FormatDataMatrix;
  138. if (barcodeFormats.HasFlag(BarcodeFormats.Ean13))
  139. formats |= Barcode.FormatEan13;
  140. if (barcodeFormats.HasFlag(BarcodeFormats.Ean8))
  141. formats |= Barcode.FormatEan8;
  142. if (barcodeFormats.HasFlag(BarcodeFormats.Itf))
  143. formats |= Barcode.FormatItf;
  144. if (barcodeFormats.HasFlag(BarcodeFormats.QRCode))
  145. formats |= Barcode.FormatQrCode;
  146. if (barcodeFormats.HasFlag(BarcodeFormats.Upca))
  147. formats |= Barcode.FormatUpcA;
  148. if (barcodeFormats.HasFlag(BarcodeFormats.Upce))
  149. formats |= Barcode.FormatUpcE;
  150. if (barcodeFormats.HasFlag(BarcodeFormats.Pdf417))
  151. formats |= Barcode.FormatPdf417;
  152. if (barcodeFormats.HasFlag(BarcodeFormats.Aztec))
  153. formats |= Barcode.FormatAztec;
  154. if (barcodeFormats.HasFlag(BarcodeFormats.All))
  155. formats = Barcode.FormatAllFormats;
  156. return formats;
  157. }
  158. internal static Size TargetResolution(CaptureQuality? captureQuality)
  159. {
  160. return captureQuality switch
  161. {
  162. CaptureQuality.Low => new Size(854, 480),
  163. CaptureQuality.Medium => new Size(1280, 720),
  164. CaptureQuality.High => new Size(1920, 1080),
  165. CaptureQuality.Highest => new Size(3840, 2160),
  166. _ => new Size(1280, 720)
  167. };
  168. }
  169. private static bool IsNeonSupported()
  170. {
  171. try
  172. {
  173. var info = File.ReadAllText("/proc/cpuinfo");
  174. return info.Contains("neon") || info.Contains("asimd");
  175. }
  176. catch (Exception)
  177. {
  178. return false;
  179. }
  180. }
  181. internal static RectF AsRectangleF(this ARectF rect)
  182. {
  183. return new(rect.Left, rect.Top, rect.Width(), rect.Height());
  184. }
  185. internal static BarcodeResult AsBarcodeResult(this Barcode barcode, CoordinateTransform? coordinateTransform = null)
  186. {
  187. RectF imageRect, previewRect;
  188. if (barcode.BoundingBox is null)
  189. {
  190. imageRect = RectF.Zero;
  191. previewRect = RectF.Zero;
  192. }
  193. else
  194. {
  195. using var barcodeBox = new ARectF(barcode.BoundingBox);
  196. imageRect = barcodeBox.AsRectangleF();
  197. if (coordinateTransform is null)
  198. {
  199. previewRect = new();
  200. }
  201. else
  202. {
  203. coordinateTransform.MapRect(barcodeBox);
  204. previewRect = barcodeBox.AsRectangleF();
  205. }
  206. }
  207. return new BarcodeResult()
  208. {
  209. BarcodeType = Methods.ConvertBarcodeResultTypes(barcode.ValueType),
  210. BarcodeFormat = (BarcodeFormats)barcode.Format,
  211. DisplayValue = barcode.DisplayValue ?? string.Empty,
  212. RawValue = barcode.RawValue ?? string.Empty,
  213. RawBytes = barcode.GetRawBytes() ?? [],
  214. PreviewBoundingBox = previewRect,
  215. ImageBoundingBox = imageRect
  216. };
  217. }
  218. }