android 如何多次使用ImageSource中的图像?

ehxuflar  于 2023-06-04  发布在  Android
关注(0)|答案(1)|浏览(264)

我正在编写一个移动的应用程序,当一个项目在平台上旋转时,可以拍摄多张照片。这将为用户提供物品的360度视图中的图片。到目前为止,我可以用Camera.Maui插件很好地拍摄多张照片,我甚至可以自动保存照片到手机的“相机”文件夹中。
我想给予用户一个选项来预览图片之前,他们得到保存。因此,我有一个页面/活动,在其中显示所有图片的“缩略图”。如果用户点击图片,它会转到另一个页面/活动,在那里我想显示全尺寸的图片,并允许用户放大/缩小和平移,然后移动到下一个/上一个图片做同样的事情。我还没有得到缩放或平移功能,因为我坚持试图在这个页面上显示图像。
问题发生在两个地方,而且是同一个问题。这些地方是当我尝试保存图像(只要我不尝试首先显示它,它就可以工作),当我尝试在第二个预览/缩放页面上显示图像时。当我第二次尝试使用从相机插件获得的ImageSource时,它说流已关闭,我意识到这是意料之中的。
我将集中精力在第二次显示图片,因为我相信保存图像将重用找到的任何解决方案。
所以,在预览过程中:MainPage有一个CameraView元素,用于显示摄像头看到的内容。我使用一个线程多次调用“GrabSnapshot”方法,该方法从相机提要中获取快照,并将其放入“pics”List中,作为我从相机获得的原始ImageSource。(我使用线程来防止主线程陷入困境,以及其他与这个问题无关的原因。
当所有的照片都拍摄完成后,预览按钮将可用,这样用户就可以转到一个新的页面/活动,该页面/活动将构建一个网格,并为拍摄的每张照片创建一个新的ImageButton。可以点击该按钮,这样整个List和所选图片的索引就会被发送到另一个页面。这个新页面尝试将选定的图片放入Image元素中,并表示流已经关闭。

**问题:**如何在不关闭原始流的情况下显示图片?

是否必须为每个ImageSource创建一个副本,并将该副本设置为ImageButtonImage元素的源?我曾经简单地尝试研究过如何做到这一点,但如果不先将其转换为byte[],然后创建一个新的流,我就找不到这样做的方法。这似乎是一个非常间接和内存昂贵的方式来做到这一点。我肯定担心内存管理,内存中的每张图片至少有2个(如果不是3个)副本。
注意:我不确定人们实际上想要多少张图片,但我认为他们可能想要20张小物品,也许30张,40张,50张或更多的大物品。我甚至不确定每张照片占用了多少内存。当前保存的图像为PNG,约为350 k至450 k,像素为720 x1462。不幸的是,它更像是一个截图,而不是一个实际的图片,所以我将尝试找到一个不同的方式来获得高分辨率的图片。我知道带有该相机的特定手机可以以4160 x3120的分辨率拍摄JPG,文件大小约为3, 800 kb,这就是我想要的相机。(我不知道这是我从相机插件中得到的,还是只是我保存它的方式,但我保存它时没有调整大小或更改DPI。
我使用C# .Net 7在Maui项目中编写应用程序。到目前为止,我只在Android上使用运行API 28和31的真实的设备进行了测试。正如我前面提到的,我正在使用Camera.Maui插件,这是我可以使用的第一个相机插件/功能。
也许我需要的是一个不同的方法/插件/功能来拍摄照片摆在首位。
在我的项目中有一堆文件/类来处理所有这些,所以我试图帕雷代码并尽可能多地合并它们。
我的代码:
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" />
*/
    }
polhcujo

polhcujo1#

从与@Jason的讨论开始,我已经将从GetSnapShot方法返回的ImageSource转换为字节数组。随后,我更改了变量数据类型以及如何设置ImageImageButton元素来显示图片。
“pics”变量现在是List<byte[]>而不是List<ImageSource>

pics.Add(camera.GetSnapShot());

成为

pics.Add(await ImageSourceToByteArray(camera.GetSnapShot()));

// https://stackoverflow.com/a/62112246/1836461
private static async Task<byte[]> ImageSourceToByteArray(ImageSource imgsrc)
{
    Stream stream = await ((StreamImageSource)imgsrc).Stream(CancellationToken.None);
    byte[] bytesAvailable = new byte[stream.Length];
    stream.Read(bytesAvailable, 0, bytesAvailable.Length);

    return bytesAvailable;
}

由于速度的需要,我可能会将方法调用分离到另一个线程中,因为我需要为这个项目快速拍照。(我希望能够以每秒2-3张的速度拍照,但我不确定这是否可以实现。最后我可能会问另一个问题,就是如何处理这个问题。)
显示图像从

foreach (ImageSource pic in pics)
{
    ...
    ImageButton button = new() { Source = pic };
    ...
}
// and
ImagePanel.Source = pics[index];

foreach (byte[] pic in pics)
{
    ...
    ImageSource display = ByteArrayToImageSourceConverter(pic);
    ImageButton button = new() { Source = display };
    ...
}
// and
ImagePanel.Source = ByteArrayToImageSourceConverter(pics[index]);

public static ImageSource ByteArrayToImageSourceConverter(byte[] imageArray)
{
    MemoryStream memory = new(imageArray);
    return ImageSource.FromStream(() => memory);
}

这是我需要的工作,所以现在已经足够了。至少在我做更多测试之前。我可能最终会保存文件并以这种方式使用它们。我担心的是,当我不再需要这些文件时,我必须设法删除它们,以及当/如果用户决定保留这些图片时,将它们移动到一个易于访问的位置。但这是另一天和/或问题。
顺便说一句,我是用ImageSource把图像保存到手机上的“相机”文件夹,或者用户选择保存它们的任何地方。我仍然没有改变保存方法,但这已经偏离了主题。我的Windows实现已经使用了一个字节数组,所以这个变化实际上会使该方法更简单。我确信有iOS和Android方法使用字节数组,但我还没有研究过,但同样,这是这个问题的题外话。
编辑/更新:
我还没有在iOS或Windows上测试过这个,但是我在Windows上保存的东西似乎在Android上工作,并且不会在iOS上编译失败,所以我已经为一个方法摆脱了OS特定的类。它至少可以从Android手机上生成一个有效的图像。我在保存PNG和JPEG之间的转换之前做了一些额外的步骤,但我也对如何调用“GetSnapShot”方法进行了更改,因此现在该方法提供PNG或JPG文件格式,而无需转换和保存非常简单。

public static async void SaveImage(byte[] imageSource, string path)
{
    await File.WriteAllBytesAsync(path, imageSource);
}

相关问题