class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback result is always null - c#

I have converted my old StartActivityForResult code to the new RegisterForActivityResult as StartActivityForResult is depreciated in the android API 29 and higher. My new code works perfectly, except that result in class ActivityResultCallback is always null. I need to be able to know when the user cancels the taking of the picture when they hit the back button, otherwise the app crashes (if a previous picture doesn't already exist) or a previously taken picture is processed again. My code (showing only the relevant code):
public class Photo : AppCompatActivity
{
public ImageView MyImageView;
public ImageButton camerabutton;
public bool PictureTaken = false;
private CurrentSubjectInfo MySubjectInfo;
private ActivityResultCallback _activityResultCallback;
private ActivityResultLauncher _activityResultLauncher;
private Uri uri;
protected override void OnCreate(Bundle bundle)
{
try
{
_activityResultCallback = new ActivityResultCallback();
_activityResultCallback.OnActivityResultCalled += ActivityResultCallback_ActivityResultCalled;
_activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.TakePicture(), _activityResultCallback);
RequestedOrientation = Android.Content.PM.ScreenOrientation.Portrait;
base.OnCreate(bundle);
SetContentView(Resource.Layout.Photo);
PictureTaken = false;
MySubjectInfo = new CurrentSubjectInfo("", "", "", "", "");
// retrieve subject information from previous activity
MySubjectInfo = Mybundle.GetParcelable("MySubjectInfo", Java.Lang.Class.FromType(typeof(CurrentSubjectInfo))) as CurrentSubjectInfo;
ImageButton camerabutton = FindViewById<ImageButton>(Resource.Id.button1);
MyImageView = FindViewById<ImageView>(Resource.Id.imageView1);
camerabutton.Click += (sender, evt) =>
{
var cameraispresent = CheckCameraHardware();
if (cameraispresent)
{
try
{
// get directory where pictures are stored
App._dir = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures);
// build file name
MySubjectInfo.Name = MySubjectInfo.Name.Replace(",", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(" ", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(".", "");
var filename = MySubjectInfo.Name + ".jpg";
App._file = new File(App._dir, String.Format(filename, Guid.NewGuid()));
uri = FileProvider.GetUriForFile(this, this.ApplicationContext.PackageName + ".provider",App._file);
// launch camera activity
_activityResultLauncher.Launch(uri);
}
catch (Exception e)
{
// trap error and log it
};
}
};
}
}
Boolean CheckCameraHardware()
{
Android.Content.PM.PackageManager pm = PackageManager;
if (pm.HasSystemFeature(Android.Content.PM.PackageManager.FeatureCamera))
{
// this device has a camera
return true;
}
else
{
// no camera on this device
return false;
}
}
private void ActivityResultCallback_ActivityResultCalled(object sender, ActivityResult result)
{
// result is always null, so I don't check it
try
{
// Because the resulting bitmap is rotated and too large, I set original orientation and resize
// suffice it to say it works perfectly so I won't post the code here
int height = 260;
int width = 200;
App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
Bitmap bitmap = App.bitmap;
var filePath = App._file.AbsolutePath;
// save the resized bitmap, overwriting original file
var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
stream.Close();
// set the imageview to the resulting bitmap
MyImageView.SetImageBitmap (App.bitmap);
// cleanup
bitmap = null;
App.bitmap = null;
PictureTaken = true;
}
catch (Exception ex)
{
PictureTaken = false;
// trap and log error
}
}
}
This is the ActivityResultCallback class:
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public EventHandler<ActivityResult> OnActivityResultCalled;
public void OnActivityResult(Java.Lang.Object result)
{
ActivityResult activityResult = result as ActivityResult;
OnActivityResultCalled?.Invoke(this, activityResult);
}
}
As stated all this code executes perfectly with no errors except that Java.Lang.Object result is always null. I need to know when the user cancels the camera activity, I would assume that Java.Lang.Object result would indicate cancelled but I get nothing. What am I missing?

Ok, after some playing around I noticed that the parameter Java.Lang.Object result in the ActivityResultCallback class was either true or false depending on what the user did with the camera. So I changed the class to:
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public EventHandler<ActivityResult> OnActivityResultCalled;
public void OnActivityResult(Java.Lang.Object result)
{
ActivityResult activityResult;
if ((bool)result)
{
activityResult = new ActivityResult((int)Result.Ok, null);
} else
{
activityResult = new ActivityResult((int)Result.Canceled, null);
}
OnActivityResultCalled?.Invoke(this, activityResult);
}
}
In the ActivityResultCallback_ActivityResultCalled function, I modified it to this:
private void ActivityResultCallback_ActivityResultCalled(object sender, ActivityResult result)
{
try
{
if (result.ResultCode == (int)Result.Ok)
{
int height = 260;
int width = 200;
App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
Bitmap bitmap = App.bitmap;
var filePath = App._file.AbsolutePath;
var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
stream.Close();
MyImageView.SetImageBitmap(App.bitmap);
bitmap = null;
App.bitmap = null;
PictureTaken = true;
}
}
catch (Exception ex)
{
// trap and log error
}
}
activityResult apparently has, at a minimum, two parameters, the result and data. I already had what I needed for data so I set that to null and cast Result.Ok or Result.Cancelled to int depending on whether result was true or false. I still don't totally understand how to use the new ActivityForResult API, but this works for me and I'm running with it.

Related

Screen capturing in C#/UWP

i am working with UWP desktop window app and I am trying to implement the functionality of taking the screenshots with saving it to a local file, but i got the error message :
System.NullReferenceException: 'Object reference not set to an instance of an object.
on await frame.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f); in the method SaveImageAsync.
Could you tell me what is going wrong?
namespace App5
{
public sealed partial class MainPage : Page
{
private SizeInt32 _lastSize;
private GraphicsCaptureItem _item;
private Direct3D11CaptureFramePool _framePool;
private GraphicsCaptureSession _session;
private CanvasDevice _canvasDevice;
private CompositionGraphicsDevice _compositionGraphicsDevice;
private Compositor _compositor;
private CompositionDrawingSurface _surface;
private CanvasBitmap _currentFrame;
private string _screenshotFilename = "test.png";
public MainPage()
{
this.InitializeComponent();
Setup();
}
private void Setup()
{
_canvasDevice = new CanvasDevice();
_compositionGraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(
Window.Current.Compositor,
_canvasDevice);
_compositor = Window.Current.Compositor;
_surface = _compositionGraphicsDevice.CreateDrawingSurface(
new Size(400, 400),
DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
var visual = _compositor.CreateSpriteVisual();
visual.RelativeSizeAdjustment = Vector2.One;
var brush = _compositor.CreateSurfaceBrush(_surface);
brush.HorizontalAlignmentRatio = 0.5f;
brush.VerticalAlignmentRatio = 0.5f;
brush.Stretch = CompositionStretch.Uniform;
visual.Brush = brush;
ElementCompositionPreview.SetElementChildVisual(this, visual);
}
public async Task StartCaptureAsync()
{
var picker = new GraphicsCapturePicker();
GraphicsCaptureItem item = await picker.PickSingleItemAsync();
if (item != null)
{
StartCaptureInternal(item);
}
}
private void StartCaptureInternal(GraphicsCaptureItem item)
{
// Stop the previous capture if we had one.
StopCapture();
_item = item;
_lastSize = _item.Size;
_framePool = Direct3D11CaptureFramePool.Create(
_canvasDevice, // D3D device
DirectXPixelFormat.B8G8R8A8UIntNormalized, // Pixel format
2, // Number of frames
_item.Size); // Size of the buffers
_framePool.FrameArrived += (s, a) =>
{
using (var frame = _framePool.TryGetNextFrame())
{
ProcessFrame(frame);
}
};
_item.Closed += (s, a) =>
{
StopCapture();
};
_session = _framePool.CreateCaptureSession(_item);
_session.StartCapture();
}
public void StopCapture()
{
_session?.Dispose();
_framePool?.Dispose();
_item = null;
_session = null;
_framePool = null;
}
private void ProcessFrame(Direct3D11CaptureFrame frame)
{
bool needsReset = false;
bool recreateDevice = false;
if ((frame.ContentSize.Width != _lastSize.Width) ||
(frame.ContentSize.Height != _lastSize.Height))
{
needsReset = true;
_lastSize = frame.ContentSize;
}
try
{
CanvasBitmap canvasBitmap = CanvasBitmap.CreateFromDirect3D11Surface(
_canvasDevice,
frame.Surface);
_currentFrame = canvasBitmap;
FillSurfaceWithBitmap(canvasBitmap);
}
catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
{
needsReset = true;
recreateDevice = true;
}
if (needsReset)
{
ResetFramePool(frame.ContentSize, recreateDevice);
}
}
private void FillSurfaceWithBitmap(CanvasBitmap canvasBitmap)
{
CanvasComposition.Resize(_surface, canvasBitmap.Size);
using (var session = CanvasComposition.CreateDrawingSession(_surface))
{
session.Clear(Colors.Transparent);
session.DrawImage(canvasBitmap);
}
}
private void ResetFramePool(SizeInt32 size, bool recreateDevice)
{
do
{
try
{
if (recreateDevice)
{
_canvasDevice = new CanvasDevice();
}
_framePool.Recreate(
_canvasDevice,
DirectXPixelFormat.B8G8R8A8UIntNormalized,
2,
size);
}
catch (Exception e) when (_canvasDevice.IsDeviceLost(e.HResult))
{
_canvasDevice = null;
recreateDevice = true;
}
} while (_canvasDevice == null);
}
private async void Button_ClickAsync(object sender, RoutedEventArgs e)
{
await StartCaptureAsync();
await SaveImageAsync(_screenshotFilename, _currentFrame);
}
private async Task SaveImageAsync(string filename, CanvasBitmap frame)
{
StorageFolder pictureFolder = KnownFolders.SavedPictures;
StorageFile file = await pictureFolder.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await frame.SaveAsync(fileStream, CanvasBitmapFileFormat.Png, 1f);
}
}
}
}
This code is from a Windows Forms .Net Core project, but I just pasted in a WPF project and it works.
In WPF you have to add Nuget package System.Drawing.Common:
You also have to add references to:
using System.Windows.Forms;
using WindowsFormsIntegration;
using System.Drawing;
// take a screenshot
Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.CopyFromScreen(0, 0, 0, 0, bitmap.Size);
// to save:
bitmap.Save(fileName);
The above code came from this project. I helped someone create sub images and search for sub images in a larger image. They had the screenshot code.
https://github.com/DataJuggler/SubImageCreator
This project is used to create sub images from a larger image and search for sub images in a larger image.

VideoCaptureElement NewVideoSample event never fired

I'm trying to use WPF-MediaKit VideoCaptureElement to preview a web cam video, and to decode QR code if one is in a frame, therefore I need to get access to each frame recorded by web cam and according to documentation it requires EnableSampleGrabbing option on and NewVideoSample event handler.
As for now my code looks something like this.
public partial class QrScannerControl : UserControl
{
public delegate void QrResultDelegate(string qr);
public QrResultDelegate OnQrDeoded { get; set; }
private QRCodeReader _qrCodeReader = new QRCodeReader();
public QrScannerControl()
{
InitializeComponent();
VideoCapElement.EnableSampleGrabbing = true;
VideoCapElement.UseYuv = false;
VideoCapElement.NewVideoSample += (sender, args) =>
{
var binBitmap = new BinaryBitmap(new HybridBinarizer(new BitmapLuminanceSource(args.VideoFrame)));
BitMatrix bm = binBitmap.BlackMatrix;
Detector detector = new Detector(bm);
DetectorResult detectorResult = detector.detect();
var retStr = detectorResult.Points.Aggregate("Found at points ", (current, point) => current + (point.ToString() + ", "));
Console.WriteLine(retStr);
DebugLabel.Content = retStr;
var result = _qrCodeReader.decode(binBitmap);
if (result == null) return;
OnQrDeoded?.Invoke(result.Text);
};
}
}
But the event never fires.

How to Save Record Video in SharpAvi

I would like to record screen video then save as Mp4,Webm or any video format.
I am using SharpAvi to record video,The code below is recording video but I am unable to save after stop ,Please help Here is my code.
namespace RecordScreenvideo
{
// Used to Configure the Recorder
public class RecorderParams
{
public RecorderParams(string filename, int FrameRate, FourCC Encoder, int Quality)
{
FileName = filename;
FramesPerSecond = FrameRate;
Codec = Encoder;
this.Quality = Quality;
Height = (int)SystemParameters.PrimaryScreenHeight;
Width = (int)SystemParameters.PrimaryScreenWidth;
}
string FileName;
public int FramesPerSecond, Quality;
FourCC Codec;
public int Height { get; private set; }
public int Width { get; private set; }
public AviWriter CreateAviWriter()
{
return new AviWriter(FileName)
{
FramesPerSecond = FramesPerSecond,
EmitIndex1 = true,
};
}
public IAviVideoStream CreateVideoStream(AviWriter writer)
{
// Select encoder type based on FOURCC of codec
if (Codec == KnownFourCCs.Codecs.Uncompressed) return writer.AddUncompressedVideoStream(Width, Height);
else if (Codec == KnownFourCCs.Codecs.MotionJpeg) return writer.AddMotionJpegVideoStream(Width, Height, Quality);
else
{
return writer.AddMpeg4VideoStream(Width, Height, (double)writer.FramesPerSecond,
// It seems that all tested MPEG-4 VfW codecs ignore the quality affecting parameters passed through VfW API
// They only respect the settings from their own configuration dialogs, and Mpeg4VideoEncoder currently has no support for this
quality: Quality,
codec: Codec,
// Most of VfW codecs expect single-threaded use, so we wrap this encoder to special wrapper
// Thus all calls to the encoder (including its instantiation) will be invoked on a single thread although encoding (and writing) is performed asynchronously
forceSingleThreadedAccess: true);
}
}
}
public class Recorder : IDisposable
{
#region Fields
AviWriter writer;
RecorderParams Params;
IAviVideoStream videoStream;
Thread screenThread;
ManualResetEvent stopThread = new ManualResetEvent(false);
#endregion
public Recorder(RecorderParams Params)
{
this.Params = Params;
// Create AVI writer and specify FPS
writer = Params.CreateAviWriter();
// Create video stream
videoStream = Params.CreateVideoStream(writer);
// Set only name. Other properties were when creating stream,
// either explicitly by arguments or implicitly by the encoder used
videoStream.Name = "Captura";
screenThread = new Thread(RecordScreen)
{
Name = typeof(Recorder).Name + ".RecordScreen",
IsBackground = true
};
screenThread.Start();
}
public void Dispose()
{
stopThread.Set();
screenThread.Join();
// Close writer: the remaining data is written to a file and file is closed
writer.Close();
stopThread.Dispose();
}
void RecordScreen()
{
var frameInterval = TimeSpan.FromSeconds(1 / (double)writer.FramesPerSecond);
var buffer = new byte[Params.Width * Params.Height * 4];
Task videoWriteTask = null;
var timeTillNextFrame = TimeSpan.Zero;
while (!stopThread.WaitOne(timeTillNextFrame))
{
var timestamp = DateTime.Now;
Screenshot(buffer);
// Wait for the previous frame is written
videoWriteTask?.Wait();
// Start asynchronous (encoding and) writing of the new frame
videoWriteTask = videoStream.WriteFrameAsync(true, buffer, 0, buffer.Length);
timeTillNextFrame = timestamp + frameInterval - DateTime.Now;
if (timeTillNextFrame < TimeSpan.Zero)
timeTillNextFrame = TimeSpan.Zero;
}
// Wait for the last frame is written
videoWriteTask?.Wait();
}
public void Screenshot(byte[] Buffer)
{
using (var BMP = new Bitmap(Params.Width, Params.Height))
{
using (var g = Graphics.FromImage(BMP))
{
g.CopyFromScreen(Point.Empty, Point.Empty, new Size(Params.Width, Params.Height), CopyPixelOperation.SourceCopy);
g.Flush();
var bits = BMP.LockBits(new Rectangle(0, 0, Params.Width, Params.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
Marshal.Copy(bits.Scan0, Buffer, 0, Buffer.Length);
BMP.UnlockBits(bits);
}
}
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
FourCC selectedCodec = KnownFourCCs.Codecs.MotionJpeg;
RecorderParams recparams = new RecorderParams("recordvideo",49,selectedCodec,100);
Recorder record = new Recorder(recparams);
}
}
}
This is very simple to save output as mp4 or any format as you can see the method`receiving file name as first argument
RecorderParams recparams = new RecorderParams("recordvideo",49,selectedCodec,100);`
So you need to just pass your output file path as
RecorderParams recparams = new RecorderParams("D:\\recordvideo.mp4",49,selectedCodec,100);`
You just need to call the dispose method
so after you start recording you either need to hard-code how much time you want it to record or make it after you press any key, here is an example of main that will start record when you press any key then it will stop recording after you press any key.
private void button1_Click(object sender, EventArgs e)
{
FourCC selectedCodec = KnownFourCCs.Codecs.MotionJpeg;
RecorderParams recparams = new RecorderParams("recordvideo",49,selectedCodec,100);
Console.WriteLine("Press any key to start recording ");
Console.ReadKey();
Recorder record = new Recorder(recparams);
Console.WriteLine("Press any key to stop recording ");
Console.ReadKey();
record.Dispose();
}

How to upload image from gallery to web api 2 in ios xamarin studio

I am making a Native IOS app using C#. I have a registration form and i am sending form data with image. but i am unable to send image as my UIImage control does not contains its path or name.I am sending Form to web api in multipart . text field data is uploading but image is not being upoloaded .
Here's how you can pick image from Camera/Gallery onto an UIImageView, and then upload;
Use UIImagePickerController for selecting image from Gallery/Camera
UIImagePickerController galleryImagePicker;
UIImagePickerController cameraImagePicker;
Use the below function on clicking a button or on your ImageView to bring a pop for to select between Camera/Gallery:
void ShowSelectPicPopup ()
{
var actionSheetAlert = UIAlertController.Create ("Select picture",
"Complete action using", UIAlertControllerStyle.ActionSheet);
actionSheetAlert.AddAction (UIAlertAction.Create ("Camera",
UIAlertActionStyle.Default, (action) => HandleCameraButtonClick ()));
actionSheetAlert.AddAction (UIAlertAction.Create ("Gallery",
UIAlertActionStyle.Default, (action) => HandleGalleryButtonClick ()));
actionSheetAlert.AddAction (UIAlertAction.Create ("Cancel", UIAlertActionStyle.Cancel,
(action) => Console.WriteLine ("Cancel button pressed.")));
// Required for iPad - You must specify a source for the Action Sheet since it is
// displayed as a popover
var presentationPopover = actionSheetAlert.PopoverPresentationController;
if (presentationPopover != null) {
presentationPopover.SourceView = View;
presentationPopover.PermittedArrowDirections = UIPopoverArrowDirection.Up;
}
PresentViewController (actionSheetAlert, true, null);
}
Now the actions:
void HandleGalleryButtonClick ()
{
if (galleryImagePicker == null) {
galleryImagePicker = new UIImagePickerController ();
galleryImagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
galleryImagePicker.MediaTypes = UIImagePickerController.AvailableMediaTypes (UIImagePickerControllerSourceType.PhotoLibrary);
galleryImagePicker.FinishedPickingMedia += Handle_FinishedPickingMedia;
galleryImagePicker.Canceled += Handle_Canceled;
}
PresentViewController (galleryImagePicker, true, () => { });
}
void HandleCameraButtonClick ()
{
if (cameraImagePicker == null) {
cameraImagePicker = new UIImagePickerController ();
cameraImagePicker.PrefersStatusBarHidden ();
cameraImagePicker.SourceType = UIImagePickerControllerSourceType.Camera;
cameraImagePicker.FinishedPickingMedia += Handle_FinishedPickingCameraMedia;
cameraImagePicker.Canceled += Handle_CameraCanceled;
}
PresentViewController (cameraImagePicker, true, () => { });
}
void Handle_Canceled (object sender, EventArgs e)
{
galleryImagePicker.DismissViewController (true, () => { });
}
protected void Handle_FinishedPickingMedia (object sender, UIImagePickerMediaPickedEventArgs e)
{
// determine what was selected, video or image
bool isImage = false;
switch (e.Info [UIImagePickerController.MediaType].ToString ()) {
case "public.image":
Console.WriteLine ("Image selected");
isImage = true;
break;
case "public.video":
Console.WriteLine ("Video selected");
break;
}
// get common info (shared between images and video)
var referenceURL = e.Info [new NSString ("UIImagePickerControllerReferenceUrl")] as NSUrl;
if (referenceURL != null)
Console.WriteLine ("Url:" + referenceURL);
// if it was an image, get the other image info
if (isImage) {
// get the original image
var originalImage = e.Info [UIImagePickerController.OriginalImage] as UIImage;
if (originalImage != null) {
// do something with the image
Console.WriteLine ("got the original image");
Picture.Image = originalImage; // Picture is the ImageView
picAssigned = true;
}
} else { // if it's a video
// get video url
var mediaURL = e.Info [UIImagePickerController.MediaURL] as NSUrl;
if (mediaURL != null) {
Console.WriteLine (mediaURL);
}
}
// dismiss the picker
galleryImagePicker.DismissViewController (true, () => { });
}
protected void Handle_FinishedPickingCameraMedia (object sender, UIImagePickerMediaPickedEventArgs e)
{
// determine what was selected, video or image
bool isImage = false;
switch (e.Info [UIImagePickerController.MediaType].ToString ()) {
case "public.image":
Console.WriteLine ("Image selected");
isImage = true;
break;
case "public.video":
Console.WriteLine ("Video selected");
break;
}
// get common info (shared between images and video)
var referenceURL = e.Info [new NSString ("UIImagePickerControllerReferenceUrl")] as NSUrl;
if (referenceURL != null)
Console.WriteLine ("Url:" + referenceURL);
// if it was an image, get the other image info
if (isImage) {
// get the original image
var originalImage = UIHelper.RotateCameraImageToProperOrientation (e.Info [UIImagePickerController.OriginalImage] as UIImage, 320);
if (originalImage != null) {
// do something with the image
Console.WriteLine ("got the original image");
Picture.Image = originalImage; // display
picAssigned = true;
}
} else { // if it's a video
// get video url
var mediaURL = e.Info [UIImagePickerController.MediaURL] as NSUrl;
if (mediaURL != null) {
Console.WriteLine (mediaURL);
}
}
// dismiss the picker
cameraImagePicker.DismissViewController (true, () => { });
}
void Handle_CameraCanceled (object sender, EventArgs e)
{
cameraImagePicker.DismissViewController (true, () => { });
}
You may need to add following two permissions in your Info.plist to access Camera/Gallery
Privacy - Camera Usage Description
Privacy - Photo Library Usage Description
They're both String, and Value can be something like "YourAppName needs access to use your camera"
And finally to upload image as multipart:
First convert the image to byte array
public static byte[] ConvertImageToByteArray(UIImage Picture) {
byte [] image = null;
try {
using (NSData imageData = Picture.Image.AsJPEG (0.5f)) { // Here you can set compression %, 0 = no compression, 1 = max compression, or the other way around, I'm not sure
image = new byte [imageData.Length];
Marshal.Copy (imageData.Bytes, image, 0, Convert.ToInt32 (imageData.Length));
} catch (Exception e) {
Console.WriteLine ("Error # Picture Byte Conversion: " + e.Message);
}
return image;
}
And finally to post the image, I use modernhttpclient
public async Task PostPicture (byte [] image)
{
try {
string url = .....;
var requestContent = new MultipartFormDataContent ();
ByteArrayContent content = content = new ByteArrayContent (image);
content.Headers.ContentType = MediaTypeHeaderValue.Parse ("image/jpeg");
requestContent.Add (content, "file", "post" + DateTime.Now + ".jpg"); // change file name as per your requirements
string result = await HttpCall.PostMultiPartContent (url, requestContent, null);
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine (ex.Message);
}
}
public class HttpCall
{
public static async Task<string> PostMultiPartContent (string url, MultipartFormDataContent content, Action<int> progressAction) // Here you can pass an handler to keep track of the upload % in your UI, I'm passing null above. (Not keeping track)
{
try {
var request = new HttpRequestMessage (HttpMethod.Post, url);
var progressContent = new ProgressableStreamContent (content, 4096, (sent, total) => {
var percentCompletion = (int)(((double)sent / total) * 100);
System.Diagnostics.Debug.WriteLine ("Completion: " + percentCompletion);
if (progressAction != null)
progressAction (percentCompletion);
});
request.Content = progressContent;
var client = new HttpClient();
var response = await client.SendAsync (request);
string result = await response.Content.ReadAsStringAsync ();
System.Diagnostics.Debug.WriteLine ("PostAsync: " + result);
return result;
} catch (Exception e) {
System.Diagnostics.Debug.WriteLine (e.Message);
return null;
}
}
}

Xamarin Android take screenshot

I am working on a project which makes drawing.
I don't use axml because I do my drawing in a class called filledpolygon and calling the function in MainActivity. I just want to take screenshot in my project. Is there any basic function, which I can call in onCreate method? So, when the program runs, it will automatically take the screenshot. I found answers except Xamarin platform.
Since Android 28 DrawingCacheEnabled is deprecated and without it we are forcing our view to to redraw on our custom canvas wich can cause artifacts with custom controls and renderers and the screenshot version might be different from what we see on screen.
The legacy code that is still working on simple cases is:
public byte[] CaptureScreenshot()
{
var view=
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView;
if (view.Height < 1 || view.Width < 1)
return null;
byte[] buffer = null;
view.DrawingCacheEnabled = true;
using (var screenshot = Bitmap.CreateBitmap(
view.Width,
view.Height,
Bitmap.Config.Argb8888))
{
var canvas = new Canvas(screenshot);
view.Draw(canvas);
using (var stream = new MemoryStream())
{
screenshot.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
view.DrawingCacheEnabled = false;
return buffer;
}
Use legacy method above as follows
if ((int)Android.OS.Build.VERSION.SdkInt < 28)
{
//legacy
}
The DrawingCacheEnabled obsolete warning redirects us to use PixelCopy. This method is acting with a callback so to use it synchronously have made some helpers:
Usage:
public byte[] CaptureScreenshot()
{
using var helper = new ScreenshotHelper(
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView,
Xamarin.Essentials.Platform.CurrentActivity);
byte[] buffer = null;
bool wait = true;
Task.Run(async () =>
{
helper.Capture((Bitmap bitmap) =>
{
try
{
if (!helper.Error)
{
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
wait = false;
}
});
}).ConfigureAwait(false);
while (wait)
{
Task.Delay(10).Wait();
}
return buffer;
}
The helper:
public class ScreenshotHelper : Java.Lang.Object, PixelCopy.IOnPixelCopyFinishedListener
{
public void OnPixelCopyFinished(int copyResult)
{
var stop = true;
if (copyResult == (int) PixelCopyResult.Success)
{
Error = false;
//todo CallbackGotScreenshot();
_callback(_bitmap);
}
else
{
Error = true;
}
_callback(_bitmap);
}
public bool Error { get; protected set; }
public ScreenshotHelper(Android.Views.View view, Activity activity)
{
_view = view;
_activity = activity;
_bitmap = Bitmap.CreateBitmap(
_view.Width,
_view.Height,
Bitmap.Config.Argb8888);
}
// Starts a background thread and its {#link Handler}.
private void StartBackgroundThread()
{
_BackgroundThread = new HandlerThread("ScreeshotMakerBackground");
_BackgroundThread.Start();
_BackgroundHandler = new Handler(_BackgroundThread.Looper);
}
// Stops the background thread and its {#link Handler}.
private void StopBackgroundThread()
{
try
{
_BackgroundThread.QuitSafely();
_BackgroundThread.Join();
_BackgroundThread = null;
_BackgroundHandler = null;
}
catch (Exception e)
{
//e.PrintStackTrace();
}
}
public void Capture(Action<Bitmap> callback)
{
//var locationOfViewInWindow = new int[2];
//_view.GetLocationInWindow(locationOfViewInWindow);
_callback = callback;
try
{
StartBackgroundThread();
//todo could create-use background handler
PixelCopy.Request(_activity.Window, _bitmap, this,
_BackgroundHandler);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Task.Run(StopBackgroundThread);
}
}
private Android.Views.View _view;
private Activity _activity;
private Bitmap _bitmap;
private HandlerThread _BackgroundThread;
private Handler _BackgroundHandler;
private Action<Bitmap> _callback;
public new void Dispose()
{
_bitmap?.Dispose();
_bitmap= null;
_activity = null;
_view = null;
_callback = null;
base.Dispose();
}
}
In your View you could run the following code which will take a screenshot. I have not tried running it in OnCreate() before so you may need to test that out to make sure the view has been fully rendered.
*Edit: According to this post you may have trouble running this code in OnCreate() so you will need to find a better place. I was unable to figure out what post the user was referring to in the link he posted.
*Edit #2: Just found out that Compress() does not take the quality parameter (which is listed as 0 below) into account since PNG is lossless, but if you change the format to JPEG for example, then you may want to turn up the quality parameter since your image will look like garbage.
public byte[] SaveImage() {
DrawingCacheEnabled = true; //Enable cache for the next method below
Bitmap bitmap = GetDrawingCache(true); //Gets the image from the cache
byte[] bitmapData;
using(MemoryStream stream = new MemoryStream()) {
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}

Categories

Resources