dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.21k stars 1.74k forks source link

Microsoft.Maui.Graphics.IImage Downsize function rotates images in Android only #22696

Open andreykoniukh opened 5 months ago

andreykoniukh commented 5 months ago

Description

When Attach image From Camera by using MediaPicker library it works fine When I try to downsize image it's rotate image by 90 degrees in ortrait mode. works fine on emulator and landscape mode but fails on real devices

Steps to Reproduce

Download provided repository Run App Use Toolbar to attach image and attach with downsize see that when you use real device, image rotates by 90 degrees if you use resize option

Link to public reproduction project repository

https://github.com/andreykoniukh/MAuiResizeImage

Version with bug

8.0.10 SR3

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Android API 34

Did you find any workaround?

No response

Relevant log output

No response

github-actions[bot] commented 5 months ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

RoiChen001 commented 5 months ago

Can't repro this issue at Android device on the latest 17.11.0 Preview 1.0(8.0.10/8.0.40). If I use the resize button, the image won't appear on device. If you have any questions about this, feel free to contact me.

andreykoniukh commented 5 months ago

Hi @andreykoniukh. We have added the "s/try-latest-version" label to this issue, which indicates that we'd like you to try and reproduce this issue on the latest available public version. This can happen because we think that this issue was fixed in a version that has just been released, or the information provided by you indicates that you might be working with an older version.

You can install the latest version by installing the latest Visual Studio (Preview) with the .NET MAUI workload installed. If the issue still persists, please let us know with any additional details and ideally a reproduction project provided through a GitHub repository.

This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

checked with latest MAUI 8.0.21 and with latest libs 8.0.21 and 8.0.40 issue is still reproducing

andreykoniukh commented 5 months ago

Can't repro this issue at Android device on the latest 17.11.0 Preview 1.0(8.0.10/8.0.40). If I use the resize button, the image won't appear on device. If you have any questions about this, feel free to contact me.

Yes I see one more issue. if I use byte array as image source, sometimes images start do disappear after adding new image into list. should I create new issue report?

hercul1017 commented 4 months ago

There is another issue of mirroring. I use Camera.Maui library to take photo. After using downsize the photo not only came out rotated but also mirrored. This only happens on Android and not on IOS. I use version 8.0.40.

W4lm4s commented 2 weeks ago

I'm facing the same issue, doesn't seem to happen on Android 13 (API33, Pixel 5, from the android emulator), but happens on Android 14 (Samsung s23, API 34 due to Android 14 ?). Behavior is the same for the Downsize() and Resize() functions.

Workload :

ID Version Source

android 34.0.113/8.0.100 VS 17.11.35303.130 aspire 8.1.0/8.0.100 VS 17.11.35303.130 maui-windows 8.0.72/8.0.100 VS 17.11.35303.130 maccatalyst 17.5.8030/8.0.100 VS 17.11.35303.130 ios 17.5.8030/8.0.100 VS 17.11.35303.130

W4lm4s commented 2 weeks ago

I just found a workaround, working on Android; I did minor adaptation to what I found on stackoverflow and the code could benefit rework. Source : https://stackoverflow.com/questions/14066038/why-does-an-image-captured-using-camera-intent-gets-rotated-on-some-devices-on-a/43669060#43669060 But I think it is missing in the downsize Implementation on MAUI ; https://github.com/dotnet/maui/blob/f86bf4771a828a41358818f10f9498870acd5524/src/Graphics/src/Graphics/Platforms/Android/GraphicsExtensions.cs#L433 A parameter or another function allowing to keep the image from the orientation it was, when taken.

1st function is the entry point to downsize image and keep the orientation.

    private static Stream DownsizeImage(Stream imageStream, string contentType)
    {
        using IImage image = PlatformImage.FromStream(imageStream);
        {
            var expectedWidth = image.Width;
            //We will downsize the image to reduce his weight
            if (expectedWidth <= 1000)
            {
                imageStream.Position = 0;
                return imageStream;
            }

            expectedWidth = 1000;
            float ratio = image.Height / image.Width;
            var downsizedImage = DownsizeHack(imageStream, image, expectedWidth, expectedWidth * ratio, contentType);
            return downsizedImage;
        }
    }

    private static Stream DownsizeHack(Stream initialStream, IImage image, float width, float height, string contentType)
    {
#if ANDROID
        initialStream.Position = 0;
        Bitmap? myBitmap = image.AsBitmap();
        var downsizedBitmap = myBitmap.Downsize((int)width, (int)height);
        var properImage = RotateIfRequired(downsizedBitmap, initialStream);

        return BitmapToStream(properImage, contentType);

        //https://stackoverflow.com/questions/14066038/why-does-an-image-captured-using-camera-intent-gets-rotated-on-some-devices-on-a/43669060#43669060
        static Bitmap RotateIfRequired(Bitmap bitmap, Stream imageStream)
        {
            var ei = new ExifInterface(imageStream);
            var orientation = ei.GetAttributeInt(ExifInterface.TagOrientation, (int)Android.Media.Orientation.Undefined);

            return orientation switch
            {
                (int)Android.Media.Orientation.Rotate90 => Rotate(bitmap, 90),
                (int)Android.Media.Orientation.Rotate180 => Rotate(bitmap, 180),
                (int)Android.Media.Orientation.Rotate270 => Rotate(bitmap, 270),
                _ => bitmap,
            };
        }
        static Bitmap Rotate(Bitmap bitmap, int angle)
        {
            var matrix = new Matrix();
            matrix.PostRotate(angle);

            return Bitmap.CreateBitmap(bitmap, 0, 0, bitmap.Width, bitmap.Height, matrix, true);
        }
        static Stream BitmapToStream(Bitmap finalImage, string contentType)
        {
            MemoryStream bos = new MemoryStream();
            finalImage.Compress(ContentTypeToAndroidCompressFormat(contentType)!, 100 /*ignored for PNG*/, bos);
            return bos;
        }
#else
        return image.Downsize(width, height).AsStream();
#endif
    }

#if ANDROID
    private static CompressFormat? ContentTypeToAndroidCompressFormat(string contentType)
    {
        return contentType switch
        {
            "image/jpeg" => CompressFormat.Jpeg,
            "image/jpg" => CompressFormat.Jpeg,
            "image/png" => CompressFormat.Png,
            _ => CompressFormat.Png
        };
    }
#endif

The namespaces :

using IImage = Microsoft.Maui.Graphics.IImage;
using Microsoft.Maui.Graphics.Platform;
using System.Diagnostics;
using File = System.IO.File;
using Path = System.IO.Path;
using ImageFormat = Microsoft.Maui.Graphics.ImageFormat;

#if ANDROID
    using Android.Graphics;
    using AndroidX.ExifInterface.Media;
    using static Android.Graphics.Bitmap;
#endif

I hope this would help someone.