Closed mossywossy closed 2 years ago
any updates?
As described in #1640: RotateImage flag should be implemented. As far as I understand the problem ist, that the image is stored ignoring the phones rotation and only the exif data in the picture contains the necessary rotation... Therefore we need the option RotateImage which should rotate the image according to the exif data and remove the rotation data from exif. Also it should be possible to retain Exif data as already requested in multiple duplicate issues (see #1640 for an overview)
Any update on this at all ?
Did anyone develop a media picker that handles the rotation bug?
Is this issue being treated?. I implemented this library and resulted to be worst than what I was using before.
i'm not sure about actuality of this information https://stackoverflow.com/questions/10850184/ios-image-get-rotated-90-degree-after-saved-as-png-representation-data but if we save photo as PNG on iOS with using UIImagePickerController we reproduce this bug
No time to lose. I ended up going to Xam.Plugin from @jamesmontemagno. That library is still working fine.
@nicoriff does it work on iOS14?
@mmunchandersen Yes in iOS 14.4 works great.
It seems I'm not the only one moving back to Xam.Plugin
@teymur371 ... no you're not the only one. For iOS I just implemented a custom dependency to take pictures and video.
I'm surprised the Xamarin team doesn't prioritize something like this.
@javanjoel I have the same impression as you have. It hs been an enigma for me why the xamarin team hasn't invested more resources on make a good camera control.
We are also going back to the plugin due to this. It appears that PickPhotoAsync has the same issue on Android so hopefully this gets addressed at the same time.
Hard to believe that a "mature" framework such as Xamarin can't even take photos properly!
Just had this bug bite me. What do we need to do to fix this?
@Hackmodford
You could try this workaround using FFImageLoading.
https://github.com/xamarin/Essentials/issues/1640#issuecomment-811915611
@bmacombe the problem with that solution is that we have to make an assumption that the image always needs to be rotated 90 deg. We cannot guarantee that will always be the case.
@javanjoel Yeah it's not perfect, could probably use the Device Display Information tools in Essentials to attempt to figure that out.
Again, just a workaround until it's correctly fixed at the platform level.
@bmacombe Tried that and it causes the opposite problem if the device is rotated.
Yes, you'll have to detect if the device is in portrait mode or not and they only rotate if needed
Here is a lib that might help with reading the exif data
https://github.com/drewnoakes/metadata-extractor-dotnet
var meta = ImageMetadataReader.ReadMetadata(await photo.OpenReadAsync());
var subIfd0Directory = meta.OfType<ExifIfd0Directory>().FirstOrDefault();
var orientation = subIfd0Directory?.GetDescription(ExifDirectoryBase.TagOrientation);
The code above will get you the orientation of the photo, then you can decide if you need to rotate
Update: might be different tags on iOS, my initial test was UWP, but at least an option to try to get them Update 2: seems the resulting stream coming back from MediaPicker.CapturePhotoAsync() on iOS doesn't have the orientation tag at all. So this method likely won't work. Update 3: using MediaPicker.PickPhotoAsync(), the stream returned does have the orientation.
Is there any update to add this feature to get the image already in correct orientation?
An effective solution to such an old bug would be greatly appreciated.
This is also a blocking point for us. Will there be any 1.7 Version? Or do we have to wait for MAUI?
Not good.
I have removed everything from Xam plugin, I have to go back to same plugin. :-(
Seems like it is resolved but not merged.
@teymur371 ... no you're not the only one. For iOS I just implemented a custom dependency to take pictures and video.
I'm surprised the Xamarin team doesn't prioritize something like this.
@teymur371 Can you give more details on how you have implemented custom dependancy ?
I have implemented custom dependency, you will start getting metadata of image
public class MediaPicker : IMediaPicker
{
public async Task<FileResult> CapturePhotoAsync(MediaPickerOptions options)
{
TaskCompletionSource<FileResult> taskCompletionSource = new TaskCompletionSource<FileResult>();
if (await Permissions.RequestAsync<Permissions.Camera>() == PermissionStatus.Granted)
{
//Create an image picker object
UIImagePickerController imagePicker = new()
{
SourceType = UIImagePickerControllerSourceType.Camera,
MediaTypes = new string[] { UTType.Image },
};
if (string.IsNullOrEmpty(options.Title))
{
imagePicker.Title = options.Title;
}
var vc = Platform.GetCurrentUIViewController();
imagePicker.AllowsEditing = false;
await vc.PresentViewControllerAsync(imagePicker, true);
imagePicker.FinishedPickingMedia += async(object sender, UIImagePickerMediaPickedEventArgs e) =>
{
UIImage uimage = e.Info[UIImagePickerController.OriginalImage] as UIImage;
var documentsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string jpgFilename = System.IO.Path.Combine(documentsDirectory, System.Guid.NewGuid() + ".jpg");
NSData imgData = uimage.AsJPEG();
NSError err = null;
await vc.DismissViewControllerAsync(true);
if (imgData.Save(jpgFilename, false, out err))
{
taskCompletionSource.TrySetResult(new FileResult(jpgFilename));
}
else
{
taskCompletionSource.TrySetException(new Exception("Unable to save the image" + err?.ToString()));
}
imagePicker?.Dispose();
imagePicker = null;
};
imagePicker.Canceled += async(object sender, EventArgs e) =>
{
await vc.DismissViewControllerAsync(true);
taskCompletionSource.TrySetResult(null);
imagePicker?.Dispose();
imagePicker = null;
};
}
else
{
taskCompletionSource.TrySetException(new PermissionException("Camera permission not granted"));
}
return await taskCompletionSource.Task;
}
class CameraDelegate : UIImagePickerControllerDelegate
{
public override void FinishedPickingMedia(UIImagePickerController picker, NSDictionary info)
{
picker.DismissModalViewController(true);
}
}
}
@glintpursuit : Your solution is worked perfectly .
Are there any fixes now? Because it still doesn't work... 😕
If anyone needs another workaround, https://stackoverflow.com/questions/54672643/xamarin-forms-camera-picture-is-left-rotated-when-comes-to-ui-in-ios This thread shows how to use MediaPlugin to avoid the issue.
Any updates on this? The app I'm developing is business critical and need this function to work as intended. Not only is the portrait an issue, but also affects landscape. The orientation or the top anchor point of the image is to the right of the camera view if it's done in portrait. So it's not a matter of it being rotated, it's not taking into account which way round the camera is orientated so it will always have one side facing up.
Any update?
@jmarti326 file = await MediaPicker.CapturePhotoAsync(); ImageSource.FromFile(file.FullPath); Solves the problem. 😂
You should not do that! If you look closely at the documentation you can see at the botton an important message. Tip: The FullPath property does not always return the physical path to the file. To get the file, use the OpenReadAsync method.
Xamarin team, any updates? News? Hello?
am I the only one who does not consider this a bug?
@dimonovdd what do you mean? Using an extension like this to get a not rly usable result, its a bug. If you would have the correct infos in the exif data of the photo to rotate it back, it would be also ok. But in this case you have no chance because the exif data is lost.
In android the image is rotated too, any solution?
This is how I fixed it in iOS. Essentially I just recreated the code with some modifications. I don't remember what the exact modification was...
https://gist.github.com/Hackmodford/9060579ccab4fd0917061ced474cdb05
and on android?
I didn't have the problem on Android. You could probably use a similar approach though.
the problem is that I do a resize and it turns on android
Is the resize operation a part of this library?
I have this combined with MediaPicker and it works fine, I have not done the iOS one yet. It sure can be improved.
public class PhotoPickerService : IPhotoPickerService
{
public async Task<byte[]> ImageToArrayAsync(string path)
{
try
{
var exif = new Android.Media.ExifInterface(path);
string orientation = exif.GetAttribute(Android.Media.ExifInterface.TagOrientation);
//Get the bitmap.
var originalImage = BitmapFactory.DecodeFile(path);
//Set imageSize and imageCompression parameters.
var imageSize = .40;
var imageCompression = 45;
//Resize it and then compress it to Jpeg.
var width = originalImage.Width * imageSize;
var height = originalImage.Height * imageSize;
var scaledImage = Bitmap.CreateScaledBitmap(originalImage, (int)width, (int)height, true);
var matrix = new Matrix();
switch (orientation)
{
case "1": // landscape
break;
case "3":
matrix.PreRotate(180);
scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true);
matrix.Dispose();
matrix = null;
break;
case "4":
matrix.PreRotate(180);
scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true);
matrix.Dispose();
matrix = null;
break;
case "5":
matrix.PreRotate(90);
scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true);
matrix.Dispose();
matrix = null;
break;
case "6": // portrait
matrix.PreRotate(90);
scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true);
matrix.Dispose();
matrix = null;
break;
case "7":
matrix.PreRotate(-90);
scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true);
matrix.Dispose();
matrix = null;
break;
case "8":
matrix.PreRotate(-90);
scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true);
matrix.Dispose();
matrix = null;
break;
}
byte[] imageBytes;
using (MemoryStream ms = new MemoryStream())
{
scaledImage.Compress(Bitmap.CompressFormat.Jpeg, imageCompression, ms);
imageBytes = ms.ToArray();
await File.WriteAllBytesAsync(path, imageBytes);
}
originalImage.Recycle();
scaledImage.Recycle();
originalImage.Dispose();
scaledImage.Dispose();
return imageBytes;
}
catch (IOException ex)
{
_ = ex.Message;
return null;
}
}
}
I have this combined with MediaPicker and it works fine, I have not done the iOS one yet. It sure can be improved.
public class PhotoPickerService : IPhotoPickerService { public async Task<byte[]> ImageToArrayAsync(string path) { try { var exif = new Android.Media.ExifInterface(path); string orientation = exif.GetAttribute(Android.Media.ExifInterface.TagOrientation); //Get the bitmap. var originalImage = BitmapFactory.DecodeFile(path); //Set imageSize and imageCompression parameters. var imageSize = .40; var imageCompression = 45; //Resize it and then compress it to Jpeg. var width = originalImage.Width * imageSize; var height = originalImage.Height * imageSize; var scaledImage = Bitmap.CreateScaledBitmap(originalImage, (int)width, (int)height, true); var matrix = new Matrix(); switch (orientation) { case "1": // landscape break; case "3": matrix.PreRotate(180); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "4": matrix.PreRotate(180); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "5": matrix.PreRotate(90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "6": // portrait matrix.PreRotate(90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "7": matrix.PreRotate(-90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "8": matrix.PreRotate(-90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; } byte[] imageBytes; using (MemoryStream ms = new MemoryStream()) { scaledImage.Compress(Bitmap.CompressFormat.Jpeg, imageCompression, ms); imageBytes = ms.ToArray(); await File.WriteAllBytesAsync(path, imageBytes); } originalImage.Recycle(); scaledImage.Recycle(); originalImage.Dispose(); scaledImage.Dispose(); return imageBytes; } catch (IOException ex) { _ = ex.Message; return null; } } }
iOS : https://github.com/xamarin/Essentials/issues/1514#issuecomment-874148026 this one can be improved too #1078
Tengo esto combinado con MediaPicker y funciona bien, todavÃa no he hecho el de iOS. Seguro que se puede mejorar.
public class PhotoPickerService : IPhotoPickerService { public async Task<byte[]> ImageToArrayAsync(string path) { try { var exif = new Android.Media.ExifInterface(path); string orientation = exif.GetAttribute(Android.Media.ExifInterface.TagOrientation); //Get the bitmap. var originalImage = BitmapFactory.DecodeFile(path); //Set imageSize and imageCompression parameters. var imageSize = .40; var imageCompression = 45; //Resize it and then compress it to Jpeg. var width = originalImage.Width * imageSize; var height = originalImage.Height * imageSize; var scaledImage = Bitmap.CreateScaledBitmap(originalImage, (int)width, (int)height, true); var matrix = new Matrix(); switch (orientation) { case "1": // landscape break; case "3": matrix.PreRotate(180); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "4": matrix.PreRotate(180); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "5": matrix.PreRotate(90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "6": // portrait matrix.PreRotate(90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "7": matrix.PreRotate(-90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; case "8": matrix.PreRotate(-90); scaledImage = Bitmap.CreateBitmap(scaledImage, 0, 0, scaledImage.Width, scaledImage.Height, matrix, true); matrix.Dispose(); matrix = null; break; } byte[] imageBytes; using (MemoryStream ms = new MemoryStream()) { scaledImage.Compress(Bitmap.CompressFormat.Jpeg, imageCompression, ms); imageBytes = ms.ToArray(); await File.WriteAllBytesAsync(path, imageBytes); } originalImage.Recycle(); scaledImage.Recycle(); originalImage.Dispose(); scaledImage.Dispose(); return imageBytes; } catch (IOException ex) { _ = ex.Message; return null; } } }
iOS: # 1514 (comentario) este también se puede mejorar # 1078
The reason for my code is that if an image caught with MediaPicker I apply a Resize, Compress, it ends up rotated.
But in this case you have no chance because the exif data is lost. @stoff99 The bug is not with the rotated image. The bug is that we don't know that the image is rotated.
I believe that the image should not be processed by Essentials in any way. The method should just return a original image without any changes and with meta data.
@dimonovdd its converted from the original to png or jpg. I'm not sure right now but its converted and in this case its loosing the meta data. Check this comment above: https://github.com/xamarin/Essentials/issues/1514#issuecomment-768087766
Bump
I have implemented custom dependency, you will start getting metadata of image
public class MediaPicker : IMediaPicker { public async Task<FileResult> CapturePhotoAsync(MediaPickerOptions options) { TaskCompletionSource<FileResult> taskCompletionSource = new TaskCompletionSource<FileResult>(); if (await Permissions.RequestAsync<Permissions.Camera>() == PermissionStatus.Granted) { //Create an image picker object UIImagePickerController imagePicker = new() { SourceType = UIImagePickerControllerSourceType.Camera, MediaTypes = new string[] { UTType.Image }, }; if (string.IsNullOrEmpty(options.Title)) { imagePicker.Title = options.Title; } var vc = Platform.GetCurrentUIViewController(); imagePicker.AllowsEditing = false; await vc.PresentViewControllerAsync(imagePicker, true); imagePicker.FinishedPickingMedia += async(object sender, UIImagePickerMediaPickedEventArgs e) => { UIImage uimage = e.Info[UIImagePickerController.OriginalImage] as UIImage; var documentsDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); string jpgFilename = System.IO.Path.Combine(documentsDirectory, System.Guid.NewGuid() + ".jpg"); NSData imgData = uimage.AsJPEG(); NSError err = null; await vc.DismissViewControllerAsync(true); if (imgData.Save(jpgFilename, false, out err)) { taskCompletionSource.TrySetResult(new FileResult(jpgFilename)); } else { taskCompletionSource.TrySetException(new Exception("Unable to save the image" + err?.ToString())); } imagePicker?.Dispose(); imagePicker = null; }; imagePicker.Canceled += async(object sender, EventArgs e) => { await vc.DismissViewControllerAsync(true); taskCompletionSource.TrySetResult(null); imagePicker?.Dispose(); imagePicker = null; }; } else { taskCompletionSource.TrySetException(new PermissionException("Camera permission not granted")); } return await taskCompletionSource.Task; } class CameraDelegate : UIImagePickerControllerDelegate { public override void FinishedPickingMedia(UIImagePickerController picker, NSDictionary info) { picker.DismissModalViewController(true); } } }
This looks great but what about IMediaPicker interface? should I create that on my own or should it get it from Essentials?
Description
After calling
CapturePhotoAsync
and setting the ImageSource to an Image, the resulting image is flipped 90 degrees (sideways) rather than retaining the orientation that the photo was taken in.Steps to Reproduce
Expected Behavior
I would expect that the photo retains the orientation the device was in when the photo was taken.
Actual Behavior
The photo is 90 degrees rotated
Basic Information
Screenshots
Reproduction Link