Список изображений / Исключение Image OutOfMemoryException
-
22-08-2019 - |
Вопрос
Я страдаю от исключения OutOfMemoryException при получении изображения из ImageList Я не смог найти подходящее решение проблемы.
У меня есть пользовательский элемент управления ListView, к которому прикреплено событие для отрисовки элементов ListViewItems.Затем это вызывает статический метод, который предназначен для рисования элемента.
Для ListView, содержащего около 300 элементов, мы получаем скачок объема памяти примерно на 100 МБ при каждом прокручивании ListView.Код - нарушитель был отслежен до следующего:
Image image = item.ImageList.Images[item.ImageKey];
if (image != null)
{
Size imageOffset = new Size((bounds.Width - image.Width) / 2, 2);
Point imagePosition = bounds.Location + imageOffset;
graphics.DrawImageUnscaled(image, imagePosition);
}
Кажется (конечно, в WinXP), что сборка мусора работает некорректно, что приводит к спирали памяти.Мы попытались добавить изображение.Dispose() непосредственно после блока кода, чтобы устранить проблему, но это не имеет никакого эффекта.
Единственное решение, которое мне удалось найти до сих пор, - это в конце статического метода вызвать GC.Collect().Однако проблема с этим заключается в том, что затем это заставляет ListView медленно перерисовывать себя, и в конечном итоге вы получаете артефакты на экране, пока он пытается перерисовать.
Кто-нибудь еще испытывал это?Или знает об обходном пути?
Решение
Вы распоряжаетесь graphics
?Кроме того, если вы утилизируете свое изображение, как вы упомянули, вам нужно будет убедиться, что оно удалено из списка изображений, иначе это вызовет дополнительные проблемы.В каком формате находятся изображения?
В общем, когда у вас возникают проблемы с нехваткой памяти при использовании изображений, ваша проблема будет заключаться либо в том, что какому-то методу не нравится определенный формат изображения, либо в 9/10 раз вы неправильно поняли жизненный цикл одного из графических объектов.
- Проверьте все ваши
Graphics
использование и поместите их вusing
блоки. - Проверьте свой
Image
жизненный цикл и будьте осторожны с их копированием, удалением, закрытием базовых потоков и т.д. - Загрузите диспетчер памяти (в VS2008 есть встроенный) и посмотрите, что не очищается должным образом.
Редактировать:
Вот лучший вариант, который я могу найти, используйте Список изображений.Нарисуйте(graphics, x, y, width, height, index)
.При этом будет использоваться внутренний дескриптор вместо создания копии изображения.
Другие советы
Мне удалось решить эту проблему в моем приложении.
У Джейсона есть ответ: вы должны убедиться, что используете блоки "using" или их эквивалент.
Я использую VB, и эквивалентом было использование Try ...Поймать...Наконец, всякий раз, когда я создавал новое растровое изображение, вызывая BitMap.Dispose и устанавливая Bitmap = nothing в части "Finally".
Похоже, это действительно распространенная проблема, судя по тем часам, которые я потратил, пытаясь найти это в Google.Приведенный ниже код также позволяет любому изображению сохранять свои пропорции при уменьшении до миниатюры, что является еще одной проблемой, которую, похоже, трудно найти в Google!
Код:
Private Function AspectedImage(ByVal ImagePath As String, ByVal SizeWanted As Integer) As Image
Dim myBitmap, WhiteSpace As System.Drawing.Bitmap
Dim myGraphics As Graphics
Dim myDestination As Rectangle
Dim MaxDimension As Integer
Dim ReductionRatio As Double
Try
'create an instance of bitmap based on a file
myBitmap = New System.Drawing.Bitmap(ImagePath)
'create a new square blank bitmap the right size
If myBitmap.Height >= myBitmap.Width Then MaxDimension = myBitmap.Height Else MaxDimension = myBitmap.Width
ReductionRatio = SizeWanted / MaxDimension
WhiteSpace = New System.Drawing.Bitmap(SizeWanted, SizeWanted)
'get the drawing surface of the new blank bitmap
myGraphics = Graphics.FromImage(WhiteSpace)
'find out if the photo is landscape or portrait
Dim WhiteGap As Double
If myBitmap.Height > myBitmap.Width Then 'portrait
WhiteGap = ((myBitmap.Width - myBitmap.Height) / 2) * -1
myDestination = New Rectangle(x:=CInt(WhiteGap * ReductionRatio), y:=0, Width:=Int(myBitmap.Width * ReductionRatio), Height:=Int(myBitmap.Height * ReductionRatio))
Else 'landscape
WhiteGap = ((myBitmap.Width - myBitmap.Height) / 2)
'create a destination rectangle
myDestination = New Rectangle(x:=0, y:=CInt(WhiteGap * ReductionRatio), Width:=Int(myBitmap.Width * ReductionRatio), Height:=Int(myBitmap.Height * ReductionRatio))
End If
'draw the image on the white square
myGraphics.DrawImage(image:=myBitmap, rect:=myDestination)
AspectedImage = WhiteSpace
Catch ex As Exception
myBitmap = New System.Drawing.Bitmap("")
AspectedImage = New System.Drawing.Bitmap(4, 4)
ImageBufferExceeded = True
MsgBox("Image Buffer exceeded, too many images in memory. If the one(s) you want can't be seen, please restart the application and navigate straight to your images")
Finally
myBitmap.Dispose()
myBitmap = Nothing
WhiteSpace = Nothing
End Try
End Function