@SUDALV

Как избежать переполнения памяти из-за операций с WriteableBitmap?

Есть страница в Windows Phone 8.1 приложении, где изначально практически ничего нет:
<Page.BottomAppBar>
        <CommandBar>
            <AppBarButton Icon="Add" x:Uid="AddPhotoAppButton" Click="AddPhotoClick" IsEnabled="{Binding IsButtonsEnabled}"/>
            <AppBarButton Icon="Send" x:Uid="SendAppButton" Click="SendClick" IsEnabled="{Binding IsButtonsEnabled}"/>
        </CommandBar>
    </Page.BottomAppBar>
    <ScrollViewer>
        <Grid>
                 <Grid Grid.Row="2">
                <TextBox />
            </Grid>
            <GridView Grid.Row="3" ItemsSource="{Binding Attachments}">
                <GridView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Holding="ListViewItem_Holding">
                            <Image Margin="10" Height="90" Width="90" Source="{Binding Image}"/>
                        </StackPanel>
                    </DataTemplate>
                </GridView.ItemTemplate>
            </GridView>
        </Grid>
    </ScrollViewer>


(Для упрощения чтения я вырезал незначимые теги и параметры)

При нажатии на кнопку в app bar пользователь может выбрать любую картинку из галереи телефона, после чего она сразу же загружается на спец сервер (клиент получает id картинки с сервера), а тело картинки записывается в ObservableCollection<FilePost>();

где

public class FilePost
    {
        public Guid ID { get; set; }
        public WriteableBitmap Image { get; set; }
    }


Данная коллекция связана с GridView на странице и превью всех картинок отображается на странице (тег Image).

Если картинка это скажем 5 мп фотография, то на телефоне с 512 мб озу после 3-4 загрузок таких картинок приложение падает с OutOfMemoryException.

Логика работы с файлом следующая:

1. Используя FileOpenPicker мы получаем указатель на StorageFile file.

2. Используем file.OpenAsync() метод чтобы открыть поток.

3. Создаём WriteableBitmap размером 1х1 (он не имеет пустого конструктора или других способов создания).

4. Читаем картинку из потока с помощью метода расширения из библиотеки WriteableBitmapEx WriteableBitmap.FromStream() - вот на этом моменте потребление памяти увеличивается примерно на 40-50 МБ (!)

5. После этого нам необходимо уменьшить картинку до 1920 по большей стороне, для чего мы используем метод расширения WriteableBitmap.Resize() из всё той же библиотеки WriteableBitmapEx. Вот на этом моменте сжирается ещё очень много памяти вплоть до OutOfMemoryException.

Дальнейшую логику видно по коду ниже. Как можно оптимизировать этот процесс? Необходимо чтобы как минимум штук 10 фотографий без проблем можно было выбрать и отобразить (например, в iOS версии этого же приложения можно свободно выбрать хоть 50 штук даже на iPhone 4s, у которого 512 мб озу).

WriteableBitmap wbmp;
using (var stream = await file.OpenAsync(FileAccessMode.Read))
{
	wbmp = new WriteableBitmap(1, 1);
        wbmp = await wbmp.FromStream(stream);
}

var ratio = wbmp.PixelWidth > wbmp.PixelHeight ? 1920.0f / wbmp.PixelWidth : 1920.0f / wbmp.PixelHeight;
if (ratio < 1)
{
	var newWidth = (int)(wbmp.PixelWidth * ratio);
        var newHeight = (int)(wbmp.PixelHeight * ratio);
        wbmp = wbmp.Resize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);

}

using (IRandomAccessStream iras = new InMemoryRandomAccessStream())
{
	await wbmp.ToStreamAsJpeg(iras);
        var bytes = new byte[iras.Size];
        var buffer = await iras.ReadAsync(bytes.AsBuffer(), (uint)iras.Size, InputStreamOptions.None);
        var response = await Uploader.UploadData(GetString("ServerApi") + "File?tokenID=" + GetToken(), buffer.ToArray());
        if (response.Item != Guid.Empty)
        	Attachments.Add(new FilePost { ID = Guid.Empty, WImage = wbmp });
}
  • Вопрос задан
  • 2794 просмотра
Пригласить эксперта
Ответы на вопрос 2
Neuroware
@Neuroware
Программист в свободное от работы время
для этих вещей существуют миниатюры - уменьшенные изображения исходного, т.к. у тебя смарт не имеет разрешения 100500 МП тебе не нужно выгружать в "эскизы" всю картинку.
Ответ написан
@SZolotov
Asp.net core, MAUI,WPF,Qt, Avalonia
Вообще-то битмапы использовать в мобильном приложении - зло, а хранить в памяти коллекции битмапов вообще не позволительная роскошь. Это избыточный и очень тяжелый формат. Для обработки изображений смотрите в сторону Nokia Imaging SDK, он как раз оптимизирован для работы с большими изображениями.

Еще я бы посоветовал избегать подобных конструкций: wbmp = wbmp.Resize ...
В процессе работы метода wbmp.Resize создается еще один битмап, а потом переменная wbmp ссылается на второй, но первый остается в памяти. По идее сборщик мусора должен подчистить память, но он может не успеть это сделать, ДО момента следующего выделения большого куска памяти. Поэтому память нужно чистить руками.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы