I'm writing a mobile app to take multiple pictures of an item as it rotates on a platform. This will give the user pictures in a 360 degree view of the item. So far, I can take the multiple pictures just fine using the Camera.Maui plugin, and I can even auto-save the pics to the phone's "Camera" folder just fine.
I want to give the user an option to preview the pics before they get saved. So, I have a page/activity where I display a "thumbnail" of all the pictures. If the user taps on a pic, it goes to another page/activity where I want to display the full-sized pic and allow the user to zoom in/out and pan around, then move to the next/previous pic to do the same. I haven't gotten to the zoom or pan features, as I'm stuck on trying to display the image on this page.
The problem happens in 2 places, and it's the same problem. Those places are when I try saving the image (which works as long as I don't try to display it first) and when I try to display the image on the 2nd preview/zoom page. When I try to use the ImageSource
I get from the camera plugin a 2nd time, it says that the stream is closed, which I realize is to be expected.
I'm going to concentrate on displaying the picture a 2nd time, as I'm sure saving the image will reuse whatever solution is found.
So, walking through the previewing process: the MainPage has a CameraView
element that displays what the camera sees. I use a thread to call the "GrabSnapshot" method multiple times, which takes a snapshot from the camera feed and puts it into the "pics" List
as the raw ImageSource
I got from the camera. (I use the thread to keep the main thread from getting bogged down, as well for other reasons unimportant to this question.)
When all the pictures are taken, the Preview button is made available so the user can go to a new page/activity that builds a grid and creates a new ImageButton
for each picture taken. That button can be clicked/tapped so the whole List
and the index of the selected picture are sent to another page. This new page tries to put the selected picture into an Image
element and where it says the stream has already been closed.
The question: How do I display the pictures without closing the original stream?
Do I have to create a copy of each ImageSource
and set the copy as the source of the ImageButton
or Image
elements? I've briefly tried to research how to do this and can't find a way to do this without first converting it into a byte[]
then creating a new stream. This seems like a very indirect and memory expensive way to do that. I'm definitely worried about memory management with having at least 2 (if not 3) copies of every picture in memory.
Note: I'm not sure how many pictures people would actually want of an item, but I'd think they might want ~20 of small items and maybe 30, 40, 50, or more of larger items. I'm not even sure how much memory each picture takes up. The current saved image is a PNG at around 350k to 450k and is 720x1462 pixels. Unfortunately, it's more like a screenshot than an actual picture, so I'm going to try to find a different way to get hi-res pictures. I know that specific phone with that camera can take a JPG at 4160x3120 resolution, with a file size of around 3,800kb, which is what I want from the camera. (I don't know if this is what I'm getting from the camera plugin or if it's just how I'm saving it, but I'm saving it without resizing it or changing the DPI.)
I'm writing the app using C# .Net 7 and in a Maui project. So far I've only tested it in Android using real devices running APIs 28 and 31. As I mentioned earlier, I'm using the Camera.Maui plugin, which was the first camera plugin/feature I could get to work.
Maybe what I need is a different approach/plugin/feature to take the pictures in the first place.
There's a bunch of files/classes in my project to handle all this, so I've tried to pare down the code and combine them as much as possible.
My code:
MainPage.xaml.cs
using Camera.MAUI;
private readonly CameraView camera;
private readonly List<ImageSource> pics = new();
public MainPage()
{
InitializeComponent();
camera = cameraView; // cameraView is the XAML x:Name of the CameraView element
}
public void GrabSnapshot()
{
pics.Add(camera.GetSnapShot());
}
MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:cv="clr-namespace:Camera.MAUI;assembly=Camera.MAUI"
x:Class="XXX.MainPage"
Title="">
<Grid VerticalOptions="Fill" HorizontalOptions="Fill">
<cv:CameraView x:Name="cameraView"
VerticalOptions="Fill"
HorizontalOptions="Fill"
CamerasLoaded="CameraView_CamerasLoaded" />
<HorizontalStackLayout Spacing="30" HorizontalOptions="Center" VerticalOptions="End" Margin="0,0,20,0" x:Name="ButtonPane">
<ImageButton Source="switch_camera_icon.jpg" HeightRequest="50" WidthRequest="60" IsOpaque="True" Clicked="SwapButton_Clicked" x:Name="SwapButton" />
<Button Text="Start" Clicked="StartButton_Clicked" x:Name="StartButton"/>
<Button Text="Options" Clicked="OptionsButton_Clicked" x:Name="OptionsButton" />
<Button Text="Preview" Clicked="PreviewButton_Clicked" x:Name="PreviewButton" IsVisible="False" IsEnabled="False" />
</HorizontalStackLayout>
</Grid>
</ContentPage>
PreviewPage.xaml.cs
private void DisplayPics()
{
// yes, this could be done in a for loop, I just haven't cleaned this up yet
int counter = 0;
/*
Grid row and column definitions for the "GridLayout"
*/
foreach (ImageSource pic in pics)
{
int row = (int)Math.Floor(counter / 2m);
int column = counter % 2;
ImageButton button = new() { Source = pic };
GridLayout.Add(button, column, row);
counter++;
}
}
/*
<Grid x:Name="GridLayout" VerticalOptions="Fill" HorizontalOptions="Fill" />
*/
ImagePage.xaml
private readonly List<ImageSource> pics = new();
private int index;
public ImagePage(int indexParam, List<ImageSource> picsList)
{
InitializeComponent();
pics = picsList;
index = indexParam;
ImagePanel.Source = pics[index];
/*
<Image x:Name="ImagePanel" VerticalOptions="Fill" HorizontalOptions="Fill" />
*/
}