From 47dff34b9ef7cb343bd5a71736c3f9418ca44022 Mon Sep 17 00:00:00 2001 From: wanggang <76527413@qq.com> Date: Tue, 25 Jun 2019 11:44:43 +0800 Subject: [PATCH] update Former-commit-id: c84c98c0255c7648475fb659f49d901425434f9c --- .../Demo.Android/Controls/CameraPreview.cs | 161 ++++++++++++++++++ .../Controls/CameraPreviewRenderer.cs | 48 ++++++ .../Demo.Android/Controls/PreviewCallback.cs | 47 +++++ .../Demo/Demo.Android/Demo.Android.csproj | 3 + .../Controls/CameraPreviewRenderer.cs | 36 ++++ .../Demo.iOS/Controls/SampleBufferDelegate.cs | 65 +++++++ .../Demo/Demo.iOS/Controls/UICameraPreview.cs | 68 ++++++++ projects/Demo/Demo.iOS/Demo.iOS.csproj | 3 + projects/Demo/Demo/Controls/CameraOptions.cs | 8 + projects/Demo/Demo/Controls/CameraPreview.cs | 22 +++ projects/Demo/Demo/Demo.csproj | 7 + projects/Demo/Demo/MainPage.xaml.cs | 41 +++-- projects/Demo/Demo/QrScanPage.xaml | 11 ++ projects/Demo/Demo/QrScanPage.xaml.cs | 14 ++ projects/IoT/IoT.Resources/wwwroot/index.html | 2 +- 15 files changed, 519 insertions(+), 17 deletions(-) create mode 100644 projects/Demo/Demo.Android/Controls/CameraPreview.cs create mode 100644 projects/Demo/Demo.Android/Controls/CameraPreviewRenderer.cs create mode 100644 projects/Demo/Demo.Android/Controls/PreviewCallback.cs create mode 100644 projects/Demo/Demo.iOS/Controls/CameraPreviewRenderer.cs create mode 100644 projects/Demo/Demo.iOS/Controls/SampleBufferDelegate.cs create mode 100644 projects/Demo/Demo.iOS/Controls/UICameraPreview.cs create mode 100644 projects/Demo/Demo/Controls/CameraOptions.cs create mode 100644 projects/Demo/Demo/Controls/CameraPreview.cs create mode 100644 projects/Demo/Demo/QrScanPage.xaml create mode 100644 projects/Demo/Demo/QrScanPage.xaml.cs diff --git a/projects/Demo/Demo.Android/Controls/CameraPreview.cs b/projects/Demo/Demo.Android/Controls/CameraPreview.cs new file mode 100644 index 00000000..950413a5 --- /dev/null +++ b/projects/Demo/Demo.Android/Controls/CameraPreview.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using Android.Content; +using Android.Hardware; +using Android.Runtime; +using Android.Views; + +namespace Demo.Droid.Controls +{ + public sealed class CameraPreview : ViewGroup, ISurfaceHolderCallback + { + private SurfaceView surfaceView; + private ISurfaceHolder holder; + private Camera.Size previewSize; + private IList supportedPreviewSizes; + private Camera camera; + private IWindowManager windowManager; + + public bool IsPreviewing { get; set; } + + public Camera Preview + { + get { return camera; } + set + { + camera = value; + if (camera != null) + { + supportedPreviewSizes = Preview.GetParameters().SupportedPreviewSizes; + RequestLayout(); + } + } + } + + public CameraPreview(Context context) + : base(context) + { + surfaceView = new SurfaceView(context); + AddView(surfaceView); + + windowManager = Context.GetSystemService(Context.WindowService).JavaCast(); + + IsPreviewing = false; + holder = surfaceView.Holder; + holder.AddCallback(this); + } + + protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) + { + int width = ResolveSize(SuggestedMinimumWidth, widthMeasureSpec); + int height = ResolveSize(SuggestedMinimumHeight, heightMeasureSpec); + SetMeasuredDimension(width, height); + + if (supportedPreviewSizes != null) + { + previewSize = GetOptimalPreviewSize(supportedPreviewSizes, width, height); + } + } + + protected override void OnLayout(bool changed, int l, int t, int r, int b) + { + var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly); + var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly); + + surfaceView.Measure(msw, msh); + surfaceView.Layout(0, 0, r - l, b - t); + } + + public void SurfaceCreated(ISurfaceHolder holder) + { + try + { + if (Preview != null) + { + Preview.SetPreviewDisplay(holder); + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(@" ERROR: ", ex.Message); + } + } + + public void SurfaceDestroyed(ISurfaceHolder holder) + { + if (Preview != null) + { + Preview.StopPreview(); + } + } + + public void SurfaceChanged(ISurfaceHolder holder, Android.Graphics.Format format, int width, int height) + { + var parameters = Preview.GetParameters(); + parameters.SetPreviewSize(previewSize.Width, previewSize.Height); + RequestLayout(); + + switch (windowManager.DefaultDisplay.Rotation) + { + case SurfaceOrientation.Rotation0: + camera.SetDisplayOrientation(90); + break; + + case SurfaceOrientation.Rotation90: + camera.SetDisplayOrientation(0); + break; + + case SurfaceOrientation.Rotation270: + camera.SetDisplayOrientation(180); + break; + } + + Preview.SetParameters(parameters); + Preview.StartPreview(); + IsPreviewing = true; + } + + private Camera.Size GetOptimalPreviewSize(IList sizes, int w, int h) + { + const double AspectTolerance = 0.1; + double targetRatio = (double)w / h; + + if (sizes == null) + { + return null; + } + + Camera.Size optimalSize = null; + double minDiff = double.MaxValue; + + int targetHeight = h; + foreach (Camera.Size size in sizes) + { + double ratio = (double)size.Width / size.Height; + + if (Math.Abs(ratio - targetRatio) > AspectTolerance) + continue; + if (Math.Abs(size.Height - targetHeight) < minDiff) + { + optimalSize = size; + minDiff = Math.Abs(size.Height - targetHeight); + } + } + + if (optimalSize == null) + { + minDiff = double.MaxValue; + foreach (Camera.Size size in sizes) + { + if (Math.Abs(size.Height - targetHeight) < minDiff) + { + optimalSize = size; + minDiff = Math.Abs(size.Height - targetHeight); + } + } + } + + return optimalSize; + } + } +} \ No newline at end of file diff --git a/projects/Demo/Demo.Android/Controls/CameraPreviewRenderer.cs b/projects/Demo/Demo.Android/Controls/CameraPreviewRenderer.cs new file mode 100644 index 00000000..fe2776fe --- /dev/null +++ b/projects/Demo/Demo.Android/Controls/CameraPreviewRenderer.cs @@ -0,0 +1,48 @@ +using Android.Content; +using Android.Hardware; +using Demo.Droid.Controls; +using Xamarin.Forms; +using Xamarin.Forms.Platform.Android; + +[assembly: ExportRenderer(typeof(Demo.Controls.CameraPreview), typeof(CameraPreviewRenderer))] + +namespace Demo.Droid.Controls +{ + public class CameraPreviewRenderer : ViewRenderer + { + private CameraPreview cameraPreview; + + public CameraPreviewRenderer(Context context) : base(context) + { + } + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + if (e.NewElement != null) + { + if (Control == null) + { + cameraPreview = new CameraPreview(Context); + SetNativeControl(cameraPreview); + } + var camera = Camera.Open((int)e.NewElement.Camera); + var parameters = camera.GetParameters(); + var width = parameters.PreviewSize.Width; + var height = parameters.PreviewSize.Height; + var format = parameters.PreviewFormat; + camera.SetPreviewCallback(new PreviewCallback(width, height, format)); + Control.Preview = camera; + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Control.Preview.Release(); + } + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/projects/Demo/Demo.Android/Controls/PreviewCallback.cs b/projects/Demo/Demo.Android/Controls/PreviewCallback.cs new file mode 100644 index 00000000..cb6e1bc0 --- /dev/null +++ b/projects/Demo/Demo.Android/Controls/PreviewCallback.cs @@ -0,0 +1,47 @@ +using Android.Hardware; +using SkiaSharp.Views.Android; +using System; +using System.IO; + +namespace Demo.Droid.Controls +{ + public class PreviewCallback : Java.Lang.Object, Camera.IPreviewCallback + { + private int width; + private int height; + private Android.Graphics.ImageFormatType previewFormat; + + public PreviewCallback(int width, int height, Android.Graphics.ImageFormatType previewFormat) + { + this.width = width; + this.height = height; + this.previewFormat = previewFormat; + } + + public void OnPreviewFrame(byte[] data, Camera camera) + { + //byte[] rotatedData = new byte[bytes.Length]; + //for (int y = 0; y < height; y++) + //{ + // for (int x = 0; x < width; x++) + // { + // rotatedData[x * height + height - y - 1] = bytes[x + y * width]; + // } + //} + var jpegBytes = this.ConvertYuvToJpeg(data); + var droidBitmap = Android.Graphics.BitmapFactory.DecodeByteArray(jpegBytes, 0, jpegBytes.Length); + var skBitmap = droidBitmap.ToSKBitmap(); + var base64 = Convert.ToBase64String(jpegBytes); + } + + private byte[] ConvertYuvToJpeg(byte[] yuvData, int quality = 80) + { + var yuv = new Android.Graphics.YuvImage(yuvData, previewFormat, width, height, null); + var ms = new MemoryStream(); + yuv.CompressToJpeg(new Android.Graphics.Rect(0, 0, width, height), quality, ms); + var jpegData = ms.ToArray(); + ms.Close(); + return jpegData; + } + } +} \ No newline at end of file diff --git a/projects/Demo/Demo.Android/Demo.Android.csproj b/projects/Demo/Demo.Android/Demo.Android.csproj index b6cc9d66..fc8c02db 100644 --- a/projects/Demo/Demo.Android/Demo.Android.csproj +++ b/projects/Demo/Demo.Android/Demo.Android.csproj @@ -88,6 +88,9 @@ + + + diff --git a/projects/Demo/Demo.iOS/Controls/CameraPreviewRenderer.cs b/projects/Demo/Demo.iOS/Controls/CameraPreviewRenderer.cs new file mode 100644 index 00000000..7398e69e --- /dev/null +++ b/projects/Demo/Demo.iOS/Controls/CameraPreviewRenderer.cs @@ -0,0 +1,36 @@ +using Demo.iOS.Controls; +using System; +using Xamarin.Forms; +using Xamarin.Forms.Platform.iOS; + +[assembly: ExportRenderer(typeof(Demo.Controls.CameraPreview), typeof(CameraPreviewRenderer))] +namespace Demo.iOS.Controls +{ + public class CameraPreviewRenderer : ViewRenderer + { + private UICameraPreview uiCameraPreview; + + protected override void OnElementChanged(ElementChangedEventArgs e) + { + base.OnElementChanged(e); + if (e.NewElement != null) + { + if (Control == null) + { + uiCameraPreview = new UICameraPreview(e.NewElement.Camera); + SetNativeControl(uiCameraPreview); + } + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Control.CaptureSession.Dispose(); + Control.Dispose(); + } + base.Dispose(disposing); + } + } +} \ No newline at end of file diff --git a/projects/Demo/Demo.iOS/Controls/SampleBufferDelegate.cs b/projects/Demo/Demo.iOS/Controls/SampleBufferDelegate.cs new file mode 100644 index 00000000..a9b64f94 --- /dev/null +++ b/projects/Demo/Demo.iOS/Controls/SampleBufferDelegate.cs @@ -0,0 +1,65 @@ +using AVFoundation; +using CoreGraphics; +using CoreMedia; +using CoreVideo; +using Foundation; +using SkiaSharp.Views.iOS; +using System; +using System.IO; +using UIKit; + +namespace Demo.iOS.Controls +{ + public class SampleBufferDelegate : AVCaptureVideoDataOutputSampleBufferDelegate + { + public override void DidOutputSampleBuffer(AVCaptureOutput captureOutput, CMSampleBuffer sampleBuffer, AVCaptureConnection connection) + { + try + { + using (var pixelBuffer = sampleBuffer.GetImageBuffer() as CVPixelBuffer) + { + pixelBuffer.Lock(CVPixelBufferLock.None); + var flags = CGBitmapFlags.PremultipliedFirst | CGBitmapFlags.ByteOrder32Little; + using (var cs = CGColorSpace.CreateDeviceRGB()) + { + using (var context = new CGBitmapContext(pixelBuffer.BaseAddress, + pixelBuffer.Width, + pixelBuffer.Height, + 8, + pixelBuffer.BytesPerRow, + cs, + (CGImageAlphaInfo)flags)) + { + using (var cgImage = context.ToImage()) + { + pixelBuffer.Unlock(CVPixelBufferLock.None); + var uiImage = UIImage.FromImage(cgImage); + using (var stream = new MemoryStream()) + { + uiImage.AsJPEG().AsStream().CopyTo(stream); + var jpegBytes = stream.ToArray(); + var skBitmap = cgImage.ToSKBitmap(); + var base64 = Convert.ToBase64String(jpegBytes); + } + } + } + } + } + } + catch (Exception ex) + { + ex.ToString(); + } + finally + { + try + { + sampleBuffer.Dispose(); + } + catch + { + } + } + } + } +} \ No newline at end of file diff --git a/projects/Demo/Demo.iOS/Controls/UICameraPreview.cs b/projects/Demo/Demo.iOS/Controls/UICameraPreview.cs new file mode 100644 index 00000000..1b297849 --- /dev/null +++ b/projects/Demo/Demo.iOS/Controls/UICameraPreview.cs @@ -0,0 +1,68 @@ +using AVFoundation; +using CoreFoundation; +using CoreGraphics; +using CoreVideo; +using Demo.Controls; +using Foundation; +using System; +using System.Linq; +using UIKit; + +namespace Demo.iOS.Controls +{ + public class UICameraPreview : UIView + { + private AVCaptureVideoPreviewLayer previewLayer; + private CameraOptions cameraOptions; + + public AVCaptureSession CaptureSession { get; private set; } + + public bool IsPreviewing { get; set; } + + public UICameraPreview(CameraOptions options) + { + cameraOptions = options; + IsPreviewing = false; + Initialize(); + } + + public override void Draw(CGRect rect) + { + base.Draw(rect); + previewLayer.Frame = rect; + } + + private void Initialize() + { + CaptureSession = new AVCaptureSession(); + previewLayer = new AVCaptureVideoPreviewLayer(CaptureSession) + { + Frame = Bounds, + VideoGravity = AVLayerVideoGravity.ResizeAspectFill + }; + + var videoDevices = AVCaptureDevice.DevicesWithMediaType(AVMediaType.Video); + var cameraPosition = (cameraOptions == CameraOptions.Front) ? AVCaptureDevicePosition.Front : AVCaptureDevicePosition.Back; + var device = videoDevices.FirstOrDefault(d => d.Position == cameraPosition); + + if (device == null) + { + return; + } + NSError error; + var input = new AVCaptureDeviceInput(device, out error); + CaptureSession.AddInput(input); + + // + var output = new AVCaptureVideoDataOutput();//CVPixelFormatType.CV32BGRA + output.WeakVideoSettings = new CVPixelBufferAttributes { PixelFormatType = CVPixelFormatType.CV32BGRA }.Dictionary; + output.SetSampleBufferDelegateQueue(new SampleBufferDelegate(), new DispatchQueue("myQueue")); + CaptureSession.AddOutput(output); + // + + Layer.AddSublayer(previewLayer); + CaptureSession.StartRunning(); + IsPreviewing = true; + } + } +} \ No newline at end of file diff --git a/projects/Demo/Demo.iOS/Demo.iOS.csproj b/projects/Demo/Demo.iOS/Demo.iOS.csproj index 10b1407e..c023b021 100644 --- a/projects/Demo/Demo.iOS/Demo.iOS.csproj +++ b/projects/Demo/Demo.iOS/Demo.iOS.csproj @@ -101,6 +101,9 @@ + + + diff --git a/projects/Demo/Demo/Controls/CameraOptions.cs b/projects/Demo/Demo/Controls/CameraOptions.cs new file mode 100644 index 00000000..ae1e6708 --- /dev/null +++ b/projects/Demo/Demo/Controls/CameraOptions.cs @@ -0,0 +1,8 @@ +namespace Demo.Controls +{ + public enum CameraOptions + { + Rear, + Front + } +} \ No newline at end of file diff --git a/projects/Demo/Demo/Controls/CameraPreview.cs b/projects/Demo/Demo/Controls/CameraPreview.cs new file mode 100644 index 00000000..c9409068 --- /dev/null +++ b/projects/Demo/Demo/Controls/CameraPreview.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xamarin.Forms; + +namespace Demo.Controls +{ + public class CameraPreview : View + { + public static readonly BindableProperty CameraProperty = BindableProperty.Create( + propertyName: "Camera", + returnType: typeof(CameraOptions), + declaringType: typeof(CameraPreview), + defaultValue: CameraOptions.Rear); + + public CameraOptions Camera + { + get { return (CameraOptions)GetValue(CameraProperty); } + set { SetValue(CameraProperty, value); } + } + } +} \ No newline at end of file diff --git a/projects/Demo/Demo/Demo.csproj b/projects/Demo/Demo/Demo.csproj index aaa99340..fec3178e 100644 --- a/projects/Demo/Demo/Demo.csproj +++ b/projects/Demo/Demo/Demo.csproj @@ -15,6 +15,7 @@ + @@ -25,4 +26,10 @@ + + + + MSBuild:UpdateDesignTimeXaml + + \ No newline at end of file diff --git a/projects/Demo/Demo/MainPage.xaml.cs b/projects/Demo/Demo/MainPage.xaml.cs index 5787bb30..de641ea2 100644 --- a/projects/Demo/Demo/MainPage.xaml.cs +++ b/projects/Demo/Demo/MainPage.xaml.cs @@ -92,10 +92,10 @@ namespace Demo e.Cancel = true; CrossLocalNotifications.Current.Show("title", e.Url); } - else if (e.Url.Contains("webqr.html")) + else if (e.Url.Contains("qr.html")) { e.Cancel = true; - Scan(); + OpenQrScanPage(); } else if (e.Url.Contains("face.html")) { @@ -118,26 +118,35 @@ namespace Demo } } - private void Scan() + //private void Scan() + //{ + // var scanPage = new ZXingScannerPage(); + // scanPage.OnScanResult += (result) => + // { + // scanPage.IsScanning = false; + // Device.BeginInvokeOnMainThread(async () => + // { + // await Navigation.PopAsync(); + // }); + // }; + // this.Navigation.PushAsync(scanPage); + //} + private async Task OpenQrScanPage() { - var scanPage = new ZXingScannerPage(); - scanPage.OnScanResult += (result) => + if (await CheckCameraPermisstions()) { - scanPage.IsScanning = false; - Device.BeginInvokeOnMainThread(async () => - { - await Navigation.PopAsync(); - }); - }; - this.Navigation.PushAsync(scanPage); + var scanPage = new QrScanPage(); + await this.Navigation.PushAsync(scanPage); + } } private async Task FaceAsync() { - if (!await CheckCameraPermisstions()) - return; - var facePage = new FacePage(new ScanningOptionsBase { UseFrontCameraIfAvailable = true }, new ZXingOverlay()); - await Navigation.PushAsync(facePage); + if (await CheckCameraPermisstions()) + { + var facePage = new FacePage(new ScanningOptionsBase { UseFrontCameraIfAvailable = true }, new ZXingOverlay()); + await Navigation.PushAsync(facePage); + } } public void Alert(object message, string title = "Ìáʾ", string button = "È·¶¨") diff --git a/projects/Demo/Demo/QrScanPage.xaml b/projects/Demo/Demo/QrScanPage.xaml new file mode 100644 index 00000000..4fd9e1f2 --- /dev/null +++ b/projects/Demo/Demo/QrScanPage.xaml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/projects/Demo/Demo/QrScanPage.xaml.cs b/projects/Demo/Demo/QrScanPage.xaml.cs new file mode 100644 index 00000000..1881a99b --- /dev/null +++ b/projects/Demo/Demo/QrScanPage.xaml.cs @@ -0,0 +1,14 @@ +using Xamarin.Forms; +using Xamarin.Forms.Xaml; + +namespace Demo +{ + [XamlCompilation(XamlCompilationOptions.Compile)] + public partial class QrScanPage : ContentPage + { + public QrScanPage() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/projects/IoT/IoT.Resources/wwwroot/index.html b/projects/IoT/IoT.Resources/wwwroot/index.html index 6bc32fa5..5b1d152f 100644 --- a/projects/IoT/IoT.Resources/wwwroot/index.html +++ b/projects/IoT/IoT.Resources/wwwroot/index.html @@ -27,7 +27,7 @@ 登录 - +