Maui icon indicating copy to clipboard operation
Maui copied to clipboard

[BUG] CamaraView Photo Rotate

Open AugPav opened this issue 1 year ago • 9 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues

Did you read the "Reporting a bug" section on Contributing file?

  • [X] I have read the "Reporting a bug" section on Contributing file: https://github.com/CommunityToolkit/Maui/blob/main/CONTRIBUTING.md#reporting-a-bug

Current Behavior

Under certain circumstances photos are taken and displayed rotated. Landscape Portrait

Expected Behavior

Photos are expected to be saved/returned with the same orientation as they were captured with cameraview.

Steps To Reproduce

Using (https://github.com/CommunityToolkit/Maui/blob/main/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs)

How to reproduce the error?

1 - Position your device in portrait and do not turn the device until further notice. 2 - Enter CameraView Page 3 - Take a photo --> the photo will be displayed correctly in the image. 4 - Put the device in landscape 5 - Take a photo --> the photo will be displayed incorrectly in the image.

Take the photo without errors.

1 - Position your device in portrait and do not turn the device until further notice. 2 - Touch CameraView Page 3 - Take a photo --> the photo will be displayed correctly in the image. 4 - Exit the CameraViewPage 5 - Put the device in landscape 6 - Enter CameraView Page 3 - Take a photo --> the photo will be displayed correctly in the image.

The problem is that when taking the photo it saves the exif horientation that the device had before entering the CameraViewPage. At least that is my conclusion.

Link to public reproduction project repository

https://github.com/CommunityToolkit/Maui/blob/main/samples/CommunityToolkit.Maui.Sample/Pages/Views/CameraView/CameraViewPage.xaml.cs

Environment

- .NET MAUI CommunityToolkit:8.0.1
- OS:Windows 10 Build 10.0.19041.0
- .NET MAUI: 8.0
. (Samsung Flip 5 Android 14 / Samsung S24 Android 14 / Samsung Galaxy A03 Core Android 13  )

Anything else?

No response

AugPav avatar Aug 06 '24 21:08 AugPav

In the following table you can see the results obtained from the different tests carried out to determine the image recording position according to the position of the device.

Description of the columns:

Start orientation: Position in which the device is located before entering the camera view.
Camera view orientation: Position in which the device is located when capturing the image.
Exif.Rotation: Orientation that is recorded in the exif.
Image:* Example of how the image looks WITH the exif.
Image WITHOUT Exif: Example of how the image looks WITHOUT the exif.
Necessary rotation: Rotation necessary for the image to be correctly oriented.
Exif.Rotation: Orientation that is recorded in the exif.
Image: Example of how the image looks WITHOUT the exif.
Subtraction logic Rear: Logic used so that the image is always in view in a well-positioned position. In which “Camera view orientation” is subtracted from “Start orientation”.

Samsung equipment used for the tests: Samsung s24, z flip, s8.
Motorola equipment used for the test: Motorola e22.

Tables:

Rear Camera:

Samsung s24, z flip, s8 Camera with Exif Data. Motorola e22 Camera without Exif Data
Device Image 1 Image 2 Logic Image 1 Logic
Orientation start Orientation camera view Exif.Rotation Image Image without Exif Necessary rotation Exif.Rotation Exif.Image Necessary rotation
90 90 Not detected Subtraction logic Rear
90 0 Not detected Subtraction logic Rear
90 -90 Not detected Subtraction logic Rear
90 180 Not detected Subtraction logic Rear
0 90 Sin rotacion Subtraction logic Rear
0 0 Sin rotacion Subtraction logic Rear
0 -90 Sin rotacion Subtraction logic Rear
0 180 Sin rotacion Subtraction logic Rear
-90 90 Not detected Subtraction logic Rear
-90 0 Not detected Subtraction logic Rear
-90 -90 Not detected Subtraction logic Rear
-90 180 Not detected Subtraction logic Rear
180 90 Not detected Subtraction logic Rear
180 0 Not detected Subtraction logic Rear
180 -90 Not detected Subtraction logic Rear
180 180 Not detected Subtraction logic Rear

Camara Frontal:

Front Subtraction Logic: Logic used to keep the image always in the correct position. In which the orientation of the device when taking the photo with the front camera is subtracted from the orientation of the device before touching the camera switch button.

Samsung Motorola
Device Image 1 Image 2 Rotacion Image 1 Rotacion
Vista camara trasera Vista camara frontal Exif.Rotation Image Image without Exif Necessary rotation Exif.Rotation Image Necessary rotation
-90 -90 Not detected Front Subtraction Logic
-90 0 Not detected Front Subtraction Logic
-90 90 Not detected Front Subtraction Logic
-90 180 Not detected Front Subtraction Logic
0 -90 Not detected Front Subtraction Logic
0 0 Not detected Front Subtraction Logic
0 90 Not detected Front Subtraction Logic
0 180 Not detected Front Subtraction Logic
90 -90 Not detected Front Subtraction Logic
90 0 Not detected Front Subtraction Logic
90 90 Not detected Front Subtraction Logic
90 180 Not detected Front Subtraction Logic
180 -90 Not detected Front Subtraction Logic
180 0 Not detected Front Subtraction Logic
180 90 Not detected Front Subtraction Logic
180 180 Not detected Front Subtraction Logic

To solve the problem on Android, after obtaining the image I leave you a code extract

    private void MyCamera_MediaCaptured(object sender, CommunityToolkit.Maui.Views.MediaCapturedEventArgs e)
    {
        try
        {
            if (Dispatcher.IsDispatchRequired)
            {
                Dispatcher.Dispatch(() =>
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        MediaCaptured(e);
                    }
                });
                return;
            }
            MediaCaptured(e);
        }
        catch (Exception ex)
        {
            var a = ex.ToString();

        }
    }

  private void MediaCaptured(CommunityToolkit.Maui.Views.MediaCapturedEventArgs e)
  {
      int ExifOrientation = 0;

      using (var memoryStream = new MemoryStream())
      {
          //Toma la foto
          e.Media.CopyTo(memoryStream);
          memoryStream.Position = 0;
          MyImage.Source = ImageSource.FromStream(() => new MemoryStream(memoryStream.ToArray()));

          memoryStream.Position = 0;
          ExifOrientation = ExifOrientation_Get(memoryStream);
          deviceOrientationPreviusPage = tPhotoDto.OrientationPreviusPage;
          int OrientationPreviusPageDegrees = Utils.Tools.ConvertDeviceDisplayRotationToDegrees(deviceOrientationPreviusPage);
          int OrientationCameraViewPageDegrees = Utils.Tools.ConvertDeviceDisplayRotationToDegrees(DeviceOrientationCameraViewPage);
          int ExifOrientationDegrees = ConvertExitOrientationToDegress(ExifOrientation);

          PhotoRotateLogic(ExifOrientation, memoryStream);
      }
  }

    private void PhotoRotateLogic(int ExifOrientation, MemoryStream memoryStream)
    {
        memoryStream.Position = 0;

        if (ExifOrientation != 0)
        {
            PhotoRotateLogicWithExif(memoryStream);
        }
        else
        {
            PhotoRotateLogicUnExif(memoryStream);
        }
        MyImageRotate.Source = ImageSource.FromStream(() => new MemoryStream(fotoBytes));
    }

    private void PhotoRotateLogicWithExif(MemoryStream memoryStream)
    {
        int rotateDegrees = 0;

        //se eliminalos exif. En funcion de la orientacion de esta page
        //se rota la foto, ya que el cameraView tiene un bug que 
        //no se orienta la foto como se correctamente.

        if (MyCamera.SelectedCamera?.Position == CameraPosition.Rear)
        {
            switch (DeviceOrientationCameraViewPage)
            {
                case 1:
                    rotateDegrees = 90;
                    break;

                case 2:
                    rotateDegrees = 0;
                    break;

                case 3:
                    rotateDegrees = -90;
                    break;

                case 4:
                    rotateDegrees = 180;
                    break;
            }
        }
        else
        {
            switch (DeviceOrientationCameraViewPage)
            {
                case 1:
                    rotateDegrees = -90;
                    break;

                case 2:
                    rotateDegrees = 0;
                    break;

                case 3:
                    rotateDegrees = 90;
                    break;

                case 4:
                    rotateDegrees = 180;
                    break;
            }

        }

        fotoBytes = RotateImage(memoryStream, rotateDegrees).ToArray();

    }

    private void PhotoRotateLogicUnExif(MemoryStream memoryStream)
    {
        int rotateDegrees = 0;

        if (MyCamera.SelectedCamera?.Position == CameraPosition.Rear)
        {
            rotateDegrees = Utils.Tools.ConvertDeviceDisplayRotationToDegrees(deviceOrientationPreviusPage) - Utils.Tools.ConvertDeviceDisplayRotationToDegrees(DeviceOrientationCameraViewPage);
        }
        else
        {
            rotateDegrees = Utils.Tools.ConvertDeviceDisplayRotationToDegrees(DeviceOrientationCameraViewPage) - Utils.Tools.ConvertDeviceDisplayRotationToDegrees(DeviceOrientationWhenSeletedCameraFront);
        }

        fotoBytes = RotateImage(memoryStream, rotateDegrees).ToArray();

    }
    public static MemoryStream RotateImage(Stream imageStream, int rotateDegrees)
    {
        if (imageStream.CanSeek)
        {
            imageStream.Seek(0, SeekOrigin.Begin);
        }

        // Cargar la imagen original desde el stream
        using (var original = SKBitmap.Decode(imageStream))
        {
            SKBitmap rotatedBitmap;

            rotatedBitmap = RotateBitmap(original, rotateDegrees);

            // Crear un nuevo MemoryStream para la imagen rotada
            var rotatedStream = new MemoryStream();
            rotatedBitmap.Encode(rotatedStream, SKEncodedImageFormat.Jpeg, 100);
            rotatedStream.Seek(0, SeekOrigin.Begin);

            rotatedStream.Position = 0;
            return rotatedStream;
        }
    }

    private static SKBitmap RotateBitmap(SKBitmap original, float degrees)
    {

        int width = degrees == 90 || degrees == -90 || degrees == 270 || degrees == -270 ? original.Height : original.Width;
        int height = degrees == 90 || degrees == -90 || degrees == 270 || degrees == -270 ? original.Width : original.Height;

        var rotatedBitmap = new SKBitmap(width, height);

        using (var surface = SKSurface.Create(new SKImageInfo(width, height)))
        {
            var canvas = surface.Canvas;
            canvas.Clear(SKColors.Transparent);

            canvas.Translate(width / 2, height / 2);
            canvas.RotateDegrees(degrees);
            canvas.Translate(-original.Width / 2, -original.Height / 2);
            canvas.DrawBitmap(original, new SKPoint(0, 0));

            using (var img = surface.Snapshot())
            using (var pixmap = img.PeekPixels())
            {
                pixmap.ReadPixels(rotatedBitmap.Info, rotatedBitmap.GetPixels(), rotatedBitmap.RowBytes);
            }
        }
        return rotatedBitmap;
    }



AugPav avatar Aug 20 '24 12:08 AugPav

Sorry we are primarily English speaking on this team. It looks like you have provided a lot of detail but I don't understand it. Would you mind translating it into English please?

bijington avatar Aug 20 '24 12:08 bijington

Sorry we are primarily English speaking on this team. It looks like you have provided a lot of detail but I don't understand it. Would you mind translating it into English please?

@bijington Done!! :)

AugPav avatar Aug 21 '24 10:08 AugPav

That is really helpful, thank you

bijington avatar Aug 21 '24 19:08 bijington

That is really helpful, thank you @bijington If you need a testing project, ask me and I will prepare it!

AugPav avatar Aug 22 '24 22:08 AugPav

I have the same problem. It behaves totally differently on different devices, so it is impossible to fix it with a simple rotate.

MiklosPathy avatar Sep 03 '24 10:09 MiklosPathy

I have the same problem. It behaves totally differently on different devices, so it is impossible to fix it with a simple rotate.

Look at the solution, I think we covered all the possibilities there

AugPav avatar Sep 16 '24 14:09 AugPav

@AugPav what is Utils.Tools that you are using?

dhindrik avatar Sep 30 '24 12:09 dhindrik

@AugPav what is Utils.Tools that you are using?

It is only used to convert the position of the device to degrees and display it on the screen for analysis, nothing relevant

AugPav avatar Sep 30 '24 14:09 AugPav

This issue still persists in .net 9, updating all packages. Does Maui people really think this problem is not relevant?

AugPav avatar Jan 16 '25 15:01 AugPav

This issue still persists in .net 9, updating all packages. Does Maui people really think this problem is not relevant?

This is an open source project worked on by the community. We openly accept PRs so please feel free to get involved and help fix it

bijington avatar Jan 16 '25 15:01 bijington

@AugPav would you mind testing your use cases against #2523 and letting me know if it resolves the issue. Thanks!

phunkeler avatar Feb 16 '25 05:02 phunkeler

I'm encountering the same issue on both iOS and Android. It would be awesome to see the PRs that fix this case approved soon!

itsazoo avatar Mar 06 '25 18:03 itsazoo