.NET MAUI Downsize image from camera before saving to gallery
Asked Answered
D

2

5

I'm trying to bumble my way through making a cross platform camera app in .Net MAUI.

I've achieved everything I want so far, but the last step is having the app resize the image before saving.

I'm trying to understand how to use Microsoft.Maui.Graphics but there's just enough missing from my understanding of C# to have it elude me.

https://learn.microsoft.com/en-us/dotnet/maui/user-interface/graphics/images

This is what I have to capture and save the image. I presume I need to use Downsize on the captured stream before saving, but it doesn't seem to matter what I try, I just get errors.

async void Button_Clicked(object sender, EventArgs e)
    {
        var result = await MediaPicker.CapturePhotoAsync();

        setAsset();

        if (result != null)
        {
            using var stream = await result.OpenReadAsync();

            using var memoryStream = new MemoryStream();

            stream.CopyTo(memoryStream);

            stream.Position = 0;   
            memoryStream.Position = 0;

            var fName = projID.Text + "-" + assID.Text + "-" + wDetail.Text + ".jpg";



#if WINDOWS
            //await System.IO.File.WriteAllBytesAsync(@"C:\Users\dentk\Desktop\VenPhotos\" + fName, memoryStream.ToArray);
#elif ANDROID
            var context = Platform.CurrentActivity;
            if (OperatingSystem.IsAndroidVersionAtLeast(29))
            {
                Android.Content.ContentResolver resolver = context.ContentResolver;
                Android.Content.ContentValues contentValues = new();
                contentValues.Put(Android.Provider.MediaStore.IMediaColumns.DisplayName, fName);
                contentValues.Put(Android.Provider.MediaStore.IMediaColumns.MimeType, "image/jpg");
                contentValues.Put(Android.Provider.MediaStore.IMediaColumns.RelativePath, "DCIM/" + "test");
                Android.Net.Uri imageUri = resolver.Insert(Android.Provider.MediaStore.Images.Media.ExternalContentUri, contentValues);
                var os = resolver.OpenOutputStream(imageUri);
                Android.Graphics.BitmapFactory.Options options = new();
                options.InJustDecodeBounds = true;
                var bitmap = Android.Graphics.BitmapFactory.DecodeStream(stream);
                bitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Jpeg, 60, os);
                os.Flush();
                os.Close();
            }
            else
            {
                Java.IO.File storagePath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim);
                string pathIO = System.IO.Path.Combine(storagePath.ToString(), fName);
                System.IO.File.WriteAllBytes(pathIO, memoryStream.ToArray());
                var mediaScanIntent = new Android.Content.Intent(Android.Content.Intent.ActionMediaScannerScanFile);
                mediaScanIntent.SetData(Android.Net.Uri.FromFile(new Java.IO.File(pathIO)));
                context.SendBroadcast(mediaScanIntent);
            }
#elif IOS || MACCATALYST
            var image = new UIKit.UIImage(Foundation.NSData.FromArray(memoryStream.ToArray()));
            image.SaveToPhotosAlbum((image, error) =>
            {
            });
#endif

            fileSaveLoc.Text = fName;

            await GetCurrentLocation();
            if (latlong != null)
            {
                double lat = Math.Round(latlong.Latitude, 6);
                double lng = Math.Round(latlong.Longitude, 6);
                latlngRes.Text = lat.ToString() + " : " + lng.ToString();

            }
            else
            {
                latlngRes.Text = "Unable to Retreive Location";
            }
        }
    }

This seems so straightforward, but I just cannot get it integrated into my existing code:

using Microsoft.Maui.Graphics.Platform;
...

IImage image;
Assembly assembly = GetType().GetTypeInfo().Assembly;
using (Stream stream = assembly.GetManifestResourceStream("GraphicsViewDemos.Resources.Images.dotnet_bot.png"))
{
    image = PlatformImage.FromStream(stream);
}

// Save image to a memory stream
if (image != null)
{
    IImage newImage = image.Downsize(150, true);
    using (MemoryStream memStream = new MemoryStream())
    {
        newImage.Save(memStream);
    }
}

Any help would be greatly appreciated.

I'm guessing getting the existing memoryStream into an IImage is the key, but how do I do that?

......
            stream.CopyTo(memoryStream);

            stream.Position = 0;   
            memoryStream.Position = 0;
            
            IImage image;

// something here to get memoryStream into image

            
            if (image != null)
            {
                IImage newImage = image.Downsize(150, true);
                using (MemoryStream memStream = new MemoryStream())
                {
                    newImage.Save(memStream);
                }
            }

// something to continue and save memStream to continue on and save into the phones gallery

......
Decathlon answered 25/5, 2023 at 0:18 Comment(3)
First, please read How to Ask before posting. Saying "I just get errors" without telling us what specific errors you are getting is extremely unhelpful. Second, have you looked at the many existing questions on this subject?Gambit
Coding in anything besides VBA is new to me. I need someone to fill in the gaps in the code I've given above, not worry about the errors I'm getting trying to piece together code from sources unknown.Decathlon
I found (when using .NET 8) that using "newImage.Save(memStream)" is not enough when saving this to a file. IImage.Save() puts the position of the destination stream to the END of the stream. You must use "memStream.Seek(0, SeekOrigin.Begin)" to reset the stream pointer allowing you to save the entire contents (e.g. when using a "memStream.CopyToAsync()" )Pauperize
T
6

I needed something similar, wasn't able to get exact compression but if the image was larger than a certain size then I downsized it to a max resolution


 private async Task<byte[]> ResizePhotoStream(FileResult photo)
        {
            byte[] result = null;

            using (var stream = await photo.OpenReadAsync())
            {
                if (stream.Length > _imageMaxSizeBytes)
                {
                    var image = PlatformImage.FromStream(stream);
                    if (image != null)
                    {
                        var newImage = image.Downsize(_imageMaxResolution, true);
                        result = newImage.AsBytes();
                    }
                }
                else
                {
                    using (var binaryReader = new BinaryReader(stream))
                    {
                        result = binaryReader.ReadBytes((int)stream.Length);
                    }
                }
            }

            return result;
        }

Tutuila answered 25/5, 2023 at 12:17 Comment(4)
at line: var image = PlatformImage.FromStream(stream); Can't find PlatformImage , it's showing error . How to fix it?Girosol
It's not available on windows. Should be in Maui.Graphics ImageTutuila
NO nowhere available,Girosol
@VishvaVijay Add this around the code: #if !WINDOWS #endifTeplitz
P
0

I went a bit further at my MAUI app on .Net 8, it works fine on iOS and Android.

using Microsoft.Maui.Media;
using Microsoft.Maui.ApplicationModel;
using Microsoft.Maui.Storage;

public async Task<FileResult> TakePhoto()
{
    if (MediaPicker.Default.IsCaptureSupported)
    {
        // check and request permissions:
        if (await Permissions.CheckStatusAsync<Permissions.Camera>() != PermissionStatus.Granted)
        {
            var result = await Permissions.RequestAsync<Permissions.Camera>();
            if (result != PermissionStatus.Granted)
                return null;
        }

        // take photo:
        var photo = await MediaPicker.Default.CapturePhotoAsync(new MediaPickerOptions { Title = "MyApp" });

        if (photo != null)
        {
            // save the file into local storage
            string localFilePath = Path.Combine(FileSystem.CacheDirectory, photo.FileName);
            using Stream sourceStream = await photo.OpenReadAsync();
            using FileStream localFileStream = File.OpenWrite(localFilePath);
            await sourceStream.CopyToAsync(localFileStream);

            return photo;
        }
    }
    return null;
}

Returned FileResult is used to display the image on the UI page:

using Microsoft.Maui.Controls;

using var stream = await mediaFile.OpenReadAsync();
var imageSource = ImageSource.FromFile(mediaFile.FullPath);
// ^ pass this value to Image binding.

As well as sending an image to backend API, which consumes Base64 string. And this is handy, because MAUI IImage class has JPEG compression for it.

using Microsoft.Maui.Graphics.Platform;

public async Task<string> ConvertToBase64(FileResult photo)
{
    // open photo as IImage:
    using var stream = await photo.OpenReadAsync();
    var image = PlatformImage.FromStream(stream);
    if (image == null) return null;

    // reduce image resolution by biggest side:
    var newImage = image.Downsize(1920f, true);
    if (newImage == null) return null;

    // compress as JPEG and save photo to local storage:
    string localFilePath = Path.Combine(FileSystem.AppDataDirectory, photo.FileName);
    using FileStream localFileStream = File.OpenWrite(localFilePath);
    await newImage.SaveAsync(localFileStream, ImageFormat.Jpeg, 0.92f);

    // compress as JPEG and convert to Base64 string:
    return newImage.AsBase64(ImageFormat.Jpeg, 0.92f);
}
Pop answered 30/10 at 15:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.