I am developing an application in phonegap and have functionality of photo capturing using Ipad camera, It works fina and all photo saved properly on server. the only issue was with the fact that they are rotated 90 degree left while saved on server.
Now i tried same thing for photos stored in library and they are upload as it is on server, so i think issue is with ipad photo capturing process. Can anyone help me to found solution for such problem. Is it Ipad normal behaviour or i have mistaken in code ?
I have used below function for photo functionality
//Capture Photo either from camera or IPAD library
function capturePhoto(source){
var deferred = $q.defer();
//When source == 1 than from Photo Library
var cameraOptions = { quality: 70, destinationType: Camera.DestinationType.FILE_URI };
if(source == 1){ cameraOptions = { quality: 70, destinationType: Camera.DestinationType.FILE_URI, sourceType: navigator.camera.PictureSourceType.PHOTOLIBRARY,targetWidth: 600,targetHeight: 600 }; }
navigator.camera.getPicture( function (imageURI) {
window.resolveLocalFileSystemURI(imageURI, function (fileEntry) {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSys) {
fileSys.root.getDirectory("auditPhotos", {create: true, exclusive: false}, function(dir) {
fileEntry.copyTo(dir,generateRandomID()+".jpg",function (entry) {
deferred.resolve(entry.fullPath)
}, null);
}, null);
}, null);
},null);
},
function (message) {
deferred.reject(message);
},
cameraOptions
);
//deferred.resolve(generateRandomID()+".jpg");
return deferred.promise;
}
As you are saving captured image (Using above function) on server in jpg format. JPG & JPEG's have a property of EXIF which store orientation of image. When you captured image through IPAD, this exif data stores camera orientation and other position related things. But this are understood by ipad only and not by browser, that's why image was seen rotated in browser which is the truth. And same image when you viewed in IPAD it was set by IPAD in proper position due to availablity of EXIF.
To solve this problem just save your image in PNG format instead of JPG and all works fine :-)
My friend had a similar problem recently, and while I'm not too sure how to specifically solve your problem, I do know that EXIF data on the image should store something about how it's oriented.
You can try implementing this library to get the EXIF data and rotate the image if needed before the upload: https://code.google.com/p/iphone-exif/
Related
I am using the cordova-plugin-camera cordova plugin (version 4.0.3), wrapped by the Ionic Native wrapper for Ionic 4 #ionic-native/camera#4. I'm using it to capture a photo (regular or from library), and move it into my app storage area for saving/sending. Things work great on Android. On iOS I continue to get a NOT_FOUND_ERR back when trying to grab the FILE_URI that comes back from the capture.
Below is how I'm capturing the image, which works just fine. The camera launches, I snap (or choose) a photo, and I get a file URI back (file:///var/mobile/Containers/Data/Application/{guid}/tmp/XYZ.jpg). Then something as simple as doing a resolveLocalFilesystemUrl on this resulting file URI will bomb with NOT_FOUND_ERR. Any advice?
const options: CameraOptions = {
quality: 100,
destinationType: this.camera.DestinationType.FILE_URI,
encodingType: this.camera.EncodingType.JPEG,
mediaType: this.camera.MediaType.PICTURE,
sourceType: (isFromLibrary) ? this.camera.PictureSourceType.PHOTOLIBRARY : this.camera.PictureSourceType.CAMERA,
saveToPhotoAlbum: false,
targetWidth: (isForDocScan) ? desiredWidth + 500 : desiredWidth,
targetHeight: (isForDocScan) ? desiredHeight + 500 : desiredHeight
}
let result = null;
try {
result = await this.camera.getPicture(options);
}
catch (err) {
this.loggingService.error('Error getting photo', err);
}
You seem to have some hiccups when using the Camera.DestinationType configs. The below are the outputs when you use FILE_URI and NATIVE_URI
Camera.DestinationType.FILE_URI
'file://' ios
'content://' android
Camera.DestinationType.NATIVE_URI
'assets-library://' ios
'content://' android
Although this is the outputs, this is internally related to your sourceType. What this means is that, if your sourceType is the camera, you should be using FILE_URI as the output is obtained from the temporary storage, while if you are using photogallery, your output needs to be obtained from the library, which is why its recommended you use NATIVE_URI
uploadImage(filePath: string, camera: boolean = false) {
try {
let options: CameraOptions;
if (camera) {
options = {
quality: 40,
destinationType: this._camera.DestinationType.DATA_URL,
encodingType: this._camera.EncodingType.JPEG,
mediaType: this._camera.MediaType.PICTURE,
correctOrientation: true
}
} else {
options = {
destinationType: this._camera.DestinationType.DATA_URL,
sourceType: this._camera.PictureSourceType.PHOTOLIBRARY,
encodingType: this._camera.EncodingType.JPEG,
mediaType: this._camera.MediaType.PICTURE
}
}
this._camera.getPicture(options).then((imageData) => {
const photo = `data:image/jpeg;base64,${imageData}`;
const fileRef = this._afs.ref(filePath);
const task = fileRef.putString(photo, 'data_url');
task.snapshotChanges().pipe(
finalize(() => {
// execute other actions
fileRef.getDownloadURL().subscribe(url => {
if (url) {
this.fileUploadUrl.next(url);
}
})
let toast = this.toastCtrl.create({
message: 'Image upload successfully',
position: 'bottom',
duration: 3000
});
toast.present();
})
).subscribe();
})
} catch (e) {
console.error(e);
let toast = this.toastCtrl.create({
message: 'Image upload cancelled',
position: 'bottom',
duration: 3000
});
toast.present();
}
}
After a picture a taken (from actual iOS device) sometimes it crashes, sometimes it works fine. If I use the front camera, it always work.
But if I use the back camera, after a picture a selected, it flashes a white screen and then the app restarts. I suspect if it’s something to do with the image size or resolution. Sometimes if I take a very low res picture with the back camera (like in a low light environment), it uploads just fine. I researched a bit online, some people suggest running the app with production mode with --prod flag, but it is not solving it.
I also tried to lower the quality value to a lower number, but that still doesn’t work with the back camera.
I am pretty sure the plugin is added correctly and the privacy settings are also correct otherwise it won't allow me to take pictures.
Yes, you are right. When taking picture using camera with DataURL (i.e Base64). we may face memory issue because of the picture size and quality. You have quality of '40' with is fine. For Width and height you can set around 300px.
The more the image size will increase the size of the image which will impact the memory.
It's not the proper solution for your query, but after wasting my nights on the same I tried to reduce the image and tried to maintain it less than 2mb and it worked quite well.You can reduce the image size by giving the targetWidth and targetHeight. I kept both of them below 500.
const options: CameraOptions = {
quality: 30, // picture quality
destinationType: this.camera.DestinationType.DATA_URL,
sourceType: this.camera.PictureSourceType.CAMERA,
encodingType: this.camera.EncodingType.PNG,
mediaType: this.camera.MediaType.PICTURE,
cameraDirection: this.camera.Direction.FRONT,
allowEdit: true,
correctOrientation :false,
targetWidth: 400,
targetHeight: 400,
}
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);
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
I am using Phonegap to retrieve a camera picture and then output it on a HTML5 canvas.
Everything works well if I use the back camera, but if I use the front camera, the image shows up upside down.
Has anyone encountered this problem? Any ideas as to why this is happening?
Is there any way I can find out which camera was used to take the picture?
Following is my code:
//take picture on click
$("#camera").click(function(){
//Take picture using device camera and retrieve image as base64-encoded string
navigator.camera.getPicture(onPhotoDataSuccess, onFail, { quality: 50 });
})
// Called when a photo is successfully retrieved in base64 format.
// It draws the photo on the canvas.
function onPhotoDataSuccess(imageData) {
var imageObj = new Image();
imageObj.onload = function(){
drawImage(cbook, this);
};
imageObj.src = imageData;
}
Thanks.