Xamarin.Forms Xam.Plugin.Media Take Picture on iOS - ios

I'm using the Xam.Plugin.Media in my Forms app to take pictures.
I take the Image stream (GetStream) and convert to a byte[] and store in my DB.
I then use that as the image source.
On Android and UWP, its working fine.
On iOS, if the picture is taken in portrait mode, the image once, selected is always rotated 90 deg.
I will later, upload this to a server and that image could be used on a different device.
For this, I also tried the GetStreamWithImageRotatedForExternalStorage but in this case, I cant see the image at all.
There are bytes in the stream (I DisplayAlert the length) but the image does not display.
Any idea what I might be doing wrong?
My code:-
private async Task TakePicture(WineDetails details)
{
await CrossMedia.Current.Initialize();
if (CrossMedia.Current.IsCameraAvailable && CrossMedia.Current.IsTakePhotoSupported)
{
var file = await CrossMedia.Current.TakePhotoAsync(new Plugin.Media.Abstractions.StoreCameraMediaOptions
{
AllowCropping = true,
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium,
SaveToAlbum = false,
RotateImage = true
});
if (file == null)
return;
using (var ms = new MemoryStream())
{
var stream = file.GetStreamWithImageRotatedForExternalStorage();
stream.CopyTo(ms);
details.LabelImage = ms.ToArray();
details.NotifyChange("ImageSource");
}
}
}
The image is updated in the page via the NotifyChange and looks like this:-
ImageSource.FromStream(() => new MemoryStream(this.LabelImage))
This works fine in all cases on Android and UWP, works on iOS using GetStream (except the image is incorrectly rotated) but does not work using GetStreamWithImageRotatedForExternalStorage on iOS.
Anyone else using this plugin?
Any idea why GetStream returns a rotated image?
Any idea why GetStreamWithImageRotatedForExternalStorage is not working?
Thanks
Update:-
Changed SaveToAlbum = true and when I open the gallery, the image is rotated 90 deg.
Have RotateImage = true which could cause the issue? I'll try setting it to false.
I still can't set the image source to the byte array of the image using GetStreamWithImageRotatedForExternalStorage.
using (var ms = new MemoryStream())
{
file.GetStreamWithImageRotatedForExternalStorage().CopyTo(ms);
details.LabelImage = ms.ToArray();
}
using the byte array for an image
return ImageSource.FromStream(() => new MemoryStream(this.LabelImage));
This does not work for me, GetStream works ok.
Update:-
Ok so, RotateImage = false + GetStreamWithImageRotatedForExternalStorage allows me to display the image but its still incorrectly rotated in my app and the gallery.

I'm using this plugin, which is similar (if not the same thing - I know James Montemagno has recently packaged/bundled his work with Xamarin).
If you check the issues board there, you'll see there are quite a few people that have similar troubles (image rotation on iOS). Almost every 'solution' mentions using GetStreamWithImageRotatedForExternalStorage.
My issue was similar - I was unable to take a photo on iOS in portrait mode, without other (non-ios Devices) rotating the image. I have tried for weeks to solve this issue, but support on the plugin seems to be quite limited.
Ultimately I had to solve this with a huge workaround - using a custom renderer extending from FFImageLoading to display our images and MetadataExtractor. We were then able to extract the EXIF data from the stream and apply a rotation transformation to the FFImageLoding image control.
The rotation information was stored in a sort of weird way, as a string. This is the method I'm using to extract the rotation information, and return the amount it needs to be rotated as an int. Note that for me, iOS was able to display the image correctly still, so it's only returned a rotation change for Android devices.
public static int GetImageRotationCorrection(byte[] image)
{
try
{
var directories = ImageMetadataReader.ReadMetadata(new MemoryStream(image));
if (Device.Android == "Android")
{
foreach (var directory in directories)
{
foreach (var tag in directory.Tags)
{
if (tag.Name == "Orientation")
{
if (tag.Description == "Top, left side(Horizontal / normal)")
return 0;
else if (tag.Description == "Left side, bottom (Rotate 270 CW)")
return 270;
else if (tag.Description == "Right side, top (Rotate 90 CW")
return 90;
}
}
}
}
return 0;
}
catch (Exception ex)
{
return 0;
}
}
Note that there is also a custom renderer for the image for FFImage Loading.
public class RotatedImage : CachedImage
{
public static BindableProperty MyRotationProperty = BindableProperty.Create(nameof(MyRotation), typeof(int), typeof(RotatedImage), 0, propertyChanged: UpdateRotation);
public int MyRotation
{
get { return (int)GetValue(MyRotationProperty); }
set { SetValue(MyRotationProperty, value); }
}
private static void UpdateRotation(BindableObject bindable, object oldRotation, object newRotation)
{
var _oldRotation = (int)oldRotation;
var _newRotation = (int)newRotation;
if (!_oldRotation.Equals(_newRotation))
{
var view = (RotatedImage)bindable;
var transformations = new System.Collections.Generic.List<ITransformation>() {
new RotateTransformation(_newRotation)
};
view.Transformations = transformations;
}
}
}
So, in my XAML - I had declared a RotatedImage instead of the standard Image. With the custom renderer, I'm able to do this and have the image display rotated the correct amount.
image.MyRotation = GetImageRotationCorrection(imageAsBytes)
It's a totally unnecessary workaround - but these are the lengths that I had to go to to get around this issue.
I'll definitely be following along with this question, there might be someone in the community who could help us both!

The SaveMetaData flag is causing the rotation issue.
Setting it to false (default is true) now displays the photo correctly.
One side effect of that, the image no longer appears in the gallery if SaveToAlbum=true.
Still can't use an image take when using GetStreamWithImageRotatedForExternalStorage, even using FFImageLoading.

I found that while using Xam.Plugin.Media v5.0.1 (https://github.com/jamesmontemagno/MediaPlugin), the combination of three different inputs produced different results on Android vs. iOS:
StoreCameraMediaOptions.SaveMetaData
StoreCameraMediaOptions.RotateImage
Using MediaFile.GetStream() vs. MediaFile.GetStreamWithImageRotatedForExternalStorage()
On Android, SaveMetaData = false, RotateImage = true, and using MediaFile.GetStreamWithImageRotatedForExternalStorage() worked for me whether I was saving the result stream externally or processing the result stream locally for display.
On iOS, the combination of RotateImage = true and StreamRotated = true would result in a NullReferenceException coming out of the plugin library. Using MediaFile.GetStreamWithImageRotatedForExternalStorage() appeared to have no impact on behaivor.
--
Before going further, it's important to understand that image orientation in the JPEG format (which Xam.Plugin.Media seems to use) isn't as straightforward as you might think. Rather than rotating the raw image bytes 90 or 180 or 270 degrees, JPEG orientation can be set through embedded EXIF metadata. Orientation issues will happen with JPEGs either if EXIF data is stripped or if downstream consumers don't handle the EXIF data properly.
The approach I landed on was to normalize JPEG image orientation at the point the image is captured without relying on EXIF metadata. This way, downstream consumers shouldn't need to be relied on to properly inspect and handle EXIF orientation metadata.
The basic solution is:
Scan a JPEG for EXIF orientation metadata
Transform the JPEG to rotate/flip as needed
Set the result JPEG's orientation metadata to default
--
Code example compatible with Xamarin, using ExifLib.Standard (1.7.0) and SixLabors.ImageSharp (1.0.4) NuGet packages. Based on (Problem reading JPEG Metadata (Orientation))
using System;
using System.IO;
using ExifLib;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Processing;
namespace Your.Namespace
{
public static class ImageOrientationUtility
{
public static Stream NormalizeOrientation(Func<Stream> inputStreamFunc)
{
using Stream exifStream = inputStreamFunc();
using var exifReader = new ExifReader(exifStream);
bool orientationTagExists = exifReader.GetTagValue(ExifTags.Orientation, out ushort orientationTagValue);
if (!orientationTagExists)
// You may wish to do something less aggressive than throw an exception in this case.
throw new InvalidOperationException("Input stream does not contain an orientation EXIF tag.");
using Stream processStream = inputStreamFunc();
using Image image = Image.Load(processStream);
switch (orientationTagValue)
{
case 1:
// No rotation required.
break;
case 2:
image.Mutate(x => x.RotateFlip(RotateMode.None, FlipMode.Horizontal));
break;
case 3:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate180, FlipMode.None));
break;
case 4:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate180, FlipMode.Horizontal));
break;
case 5:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate90, FlipMode.Horizontal));
break;
case 6:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate90, FlipMode.None));
break;
case 7:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate270, FlipMode.Horizontal));
break;
case 8:
image.Mutate(x => x.RotateFlip(RotateMode.Rotate270, FlipMode.None));
break;
}
image.Metadata.ExifProfile.SetValue(ExifTag.Orientation, (ushort)1);
var outStream = new MemoryStream();
image.Save(outStream, new JpegEncoder{Quality = 100});
outStream.Position = 0;
return outStream;
}
}
}
And to use in conjunction with Xam.Plugin.Media:
MediaFile photo = await CrossMedia.Current.TakePhotoAsync(options);
await using Stream stream = ImageOrientationUtility.NormalizeOrientation(photo.GetStream);

Related

Getting video Rotation metadata value using JavaCV

It seems there are lots of related topics out there, but I was not able to find the answer I'm searching for.
I'm using JavaCV and FFmpegFrameGrabber to get an image from the middle of the video. If an mp4 file has a "Rotation" metadata field (like 90 or 270), I'm getting an image that is positioned not correctly. I wanted to get Orientation from FFmpegFrameGrabber, but could not find a way to do so.
Is there a way to tell FFmpegFrameGrabber to respect orientation, or is there a way to get this value somehow using JavaCV?
Just in case the code that I have so far
FFmpegFrameGrabber g = new FFmpegFrameGrabber(input);
g.start();
g.getVideoMetadata(); // <-- this thing is empty
try {
g.setFrameNumber(g.getLengthInFrames() / 2);
Java2DFrameConverter converter = new Java2DFrameConverter();
Frame frame = g.grabImage();
BufferedImage bufferedImage = converter.convert(frame);
ImageIO.write(bufferedImage, "jpeg", output);
} finally {
g.stop();
}

Unity 3d 2017.2.1 camera feed looks zoomed in iPad

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraFeed : MonoBehaviour {
// Use this for initialization
public Renderer rend;
void Start () {
WebCamDevice[] devices = WebCamTexture.devices;
// for debugging purposes, prints available devices to the console
for(int i = 0; i < devices.Length; i++)
{
print("Webcam available: " + devices[i].name);
}
Renderer rend = this.GetComponentInChildren<Renderer>();
// assuming the first available WebCam is desired
WebCamTexture tex = new WebCamTexture(devices[0].name);
rend.material.mainTexture = tex;
//rend.material.SetTextureScale("tex", new Vector2(1.2f, 1.2f));
tex.Play();
}
// Update is called once per frame
void Update () {
}
}
I use the above code to render camera feed on a plane. The code works fine but I see zoomed feed(around 1.2x) when deployed to iPad. How can I get the normal feed. Or How do I zoom out the camera feed
According to https://answers.unity.com/questions/1421387/webcamtexture-zoomed-in-on-ipad-pro.html if you insert this code it should scale properly:
rend.material.SetTextureScale("_Texture", new Vector2(1f, 1f))
Furthermore, it seems that you were trying something like this but your vector was off (by 0.2f) though you have commented out that line as well. You could reduce the scale even more though that could make the image just smaller if the camera couldn't support that zoomout :).

Shrink image in iphone 7 xamarin ios?

I am working in xamarin.ios. I am capturing a picture with help of camera and trying to upload it on server. But when I am trying to upload it on server I am getting "TargetInvocationException". But when I am running same code on Ipad everything is working fine.
Following is the code :
Camera.TakePicture(this, (obj) =>
{
var photo = obj.ValueForKey(new NSString("UIImagePickerControllerOriginalImage")) as UIImage;
Byte[] myByteArray;
using (NSData imageData = photo.AsJPEG(0.0f))
{
//myByteArray = imageData.ToArray();
myByteArray = new Byte[imageData.Length];
System.Runtime.InteropServices.Marshal.Copy(imageData.Bytes, myByteArray, 0, Convert.ToInt32(imageData.Length));
}
ImageLoaderPopup imageLoader = new ImageLoaderPopup(this, selectedWorkOrder, myByteArray, title);
imageLoader.PopUp(true, delegate { });
});
Does anyone know why I am facing this problem? What am I doing wrong?
You might be having problem with the size of the image returned by the Picker. Images taken with the iPhone 7 are huge in size.
You just need to scale down the original image before uploading it using the Scale method and setting the size you find acceptable.
var smallImage = image.Scale(new CGSize(1280, 720)); //Or the size you need.
UPDATE
All you need is the Scale method mentioned above.
The big advantage of Xamarin being Open Source is that you can look at the internals any time you have a doubt.
https://github.com/xamarin/xamarin-macios/blob/master/src/UIKit/UIImage.cs#L66

Image brightness/contrast - Xamarin iOS

Xamarin provides some sample code for doing simple adjustments to an image in iOS:
https://github.com/xamarin/recipes/blob/master/ios/media/coreimage/adjust_contrast_and_brightness_of_an_image/color_controls_pro/ImageViewController.cs
This code updates the image only when the user lets go of the slider knob - not the continuous updating we normally expect.
However when I make the following change I reliably get SIGSEGV faults on hardware.
//sliderC.TouchUpInside += HandleValueChanged;
//sliderS.TouchUpInside += HandleValueChanged;
//sliderB.TouchUpInside += HandleValueChanged;
sliderC.ValueChanged += HandleValueChanged;
sliderS.ValueChanged += HandleValueChanged;
sliderB.ValueChanged += HandleValueChanged;
I expect that this is "overloading" the code in some way. How would you implement image adjustments that avoid this problem? Is there a lower-level approach, or do other apps simply use a much lower-rez version of the image for adjustments?
Here is a quickie version that I did that is 'more' realtime (This is a recording from the sim, the device (6s) is smooth depending upon initial image size.
Create a single viewcontroller iOS app from the template and add a UIImageView and three sliders to the storyboard so it looks like the animate gif.
I created a simple class to store the ColorCtrl values (brightness, contrast, saturation:
public class ColorCtrl
{
public float s;
public float b;
public float c;
}
Than in the ViewDidLoad method, do some setup:
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
string filePath = Path.Combine (NSBundle.MainBundle.BundlePath, "hero.jpg");
originalImage = new CIImage (new NSUrl (filePath, false));
colorCtrls = new CIColorControls ();
colorCtrls.Image = originalImage;
// Create the context only once, and re-use it
var contextOptions = new CIContextOptions ();
contextOptions.UseSoftwareRenderer = false; // gpu vs. cpu
// On save of the image, create a new context with highqual attributes and re-apply the filter...
contextOptions.HighQualityDownsample = false;
contextOptions.PriorityRequestLow = false; // high queue order it
contextOptions.CIImageFormat = (int)CIFormat.ARGB8; // use 32bpp, vs. 64|128bpp
context = CIContext.FromOptions (contextOptions);
}
Then for the three slider's change handlers.
Within those I 'hack' a busy flag in order to skip the image transformation if the last transform is not done get. If we are not busy, then do an await call on our async transform method.
Note: I said 'hack', I am mean it, in a best practice way, this should pump transform requests to a queue and the queue handler would summarize all the pending items in the queue, flush them and do the transform.
Note: I added the "async" to the generated event handlers so I can await the image transform.
Note: The three slider's handlers are all the same except for the value that they are assigning; colorCtrlV.b | colorCtrlV.s | colorCtrlV.c
Note: You can down-sample large image at the moment the user does a touch down, perform the transforms on that, and on touch up, transform the original full-size image...
async partial void brightnessChange (UISlider sender)
{
if (!busy) {
busy = true;
colorCtrlV.b = sender.Value;
this.imageView.Image = await FilterImageAsync (colorCtrlV);
busy = false;
}
}
Ok, now for actual transform, a fairly simple Run.Task so we can get this work off the main thread and NOT block the UI. This way the sliders will not stutter as the user slides their finger, BUT due to the 'busy' flag/hack in the slider's handler we might skip some of those events (we should add a handler for the touch drag exit so we process the 'last' user's requested value....)
// async Task.Run() - not best practice - just a demo
async Task<UIImage> FilterImageAsync (ColorCtrl value)
{
if (transformImage == null)
transformImage = new Func<UIImage>(() => {
colorCtrls.Brightness = colorCtrlV.b;
colorCtrls.Saturation = colorCtrlV.s;
colorCtrls.Contrast = colorCtrlV.c;
var output = colorCtrls.OutputImage;
var cgImage = context.CreateCGImage (output, originalImage.Extent);
var filteredImage = new UIImage (cgImage);
return filteredImage;
});
UIImage image = await Task.Run<UIImage>(transformImage);
return image;
}
Personally For this type of realtime image transformations I prefer to do it via OpenGL-ES using GPUImage as the screen's interaction at the 60z refresh rate is as smooth as butter, but it is a lot more work than using CoreImage filters.

How we can implement object recognition in real time through Augmented Reality way?

I need to implement image recognition in real time. So I have tried metaio SDK for Augmented reality. In Metaio SDK I have used extended image tracking. But through that, only one image and text to that image can be added in static. So that if I scan that image, I can get the text. How can we achieve this dynamically for object recognition through Augmented Reality way. Are there any other SDK's(trial versions) that supports object recognition and 3D tracking.
Help is highly appreciable.
Thanks in advance,
laxmi.
Well, I don't know about Metaio, but Vuforia has this feature you seek.
Vuforia has this concept of User-defined targets. You can download the Vuforia SDK or Unity extension of Vuforia ot do so.
I personally use Vuforia Unity and the ARCamera from there just needs to be changed so that it fits the User Defined settings.
After that you simply need to add this script that allows access to the camera Images as follows:
using UnityEngine;
using System.Collections;
public class CameraImageAccess : MonoBehaviour
{
private Image.PIXEL_FORMAT m_PixelFormat = Image.PIXEL_FORMAT.RGB888;
private bool m_RegisteredFormat = false;
private bool m_LogInfo = true;
void Start()
{
QCARBehaviour qcarBehaviour = (QCARBehaviour) FindObjectOfType(typeof(QCARBehaviour));
if (qcarBehaviour)
{
qcarBehaviour.RegisterTrackablesUpdatedCallback(OnTrackablesUpdated);
}
}
public void OnTrackablesUpdated()
{
if (!m_RegisteredFormat)
{
CameraDevice.Instance.SetFrameFormat(m_PixelFormat, true);
m_RegisteredFormat = true;
}
if (m_LogInfo)
{
CameraDevice cam = CameraDevice.Instance;
Image image = cam.GetCameraImage(m_PixelFormat);
if (image == null)
{
Debug.Log(m_PixelFormat + " image is not available yet");
}
else
{
string s = m_PixelFormat + " image: \n";
s += " size: " + image.Width + "x" + image.Height + "\n";
s += " bufferSize: " + image.BufferWidth + "x" + image.BufferHeight + "\n";
s += " stride: " + image.Stride;
Debug.Log(s);
m_LogInfo = false;
}
}
}
If you're planning to use the Vuforia SDK instead then you should Use the image class so works much like the native version.
Register for the desired image format using the CameraDevice.SetFrameFormat method: CameraDevice.Instance.SetFrameFormat(Image.PIXEL_FORMAT.RGB888, true);
Call this method after QCARBehaviour has a chance to run its Start method.
Use the Unity script-ordering feature, or do this once in an Update callback.
Retrieve the image using the CameraDevice.GetCameraImage method.
Take this action from the ITrackerEventHandler.OnTrackablesUpdated callback. That way you can ensure that you retrieve the latest camera image that matches the current frame.
Always make sure that the camera image is not null, since it can take a few frames for the image to become available after registering for an image format.
Thumbs up, if this was helpful.
Take a look at ARtoolkit (open source), which can recognize and track your own images (So only 2d surfaces, not 3d objects) using Natural Feature Tracking.
Another alternative is OpenCV, which is a huge open source computer vision library.

Resources