Проблемы с памятью создание растрового изображения в VB .NET
Мое приложение считывает файл (обычно обычный текст), затем сжимает его, а затем создает растровое изображение из сжатых данных.
До размера файла около 30 МБ текста (примерно 15 МБ сжатого) он работает так же, как и ожидалось.
При 40 МБ текста (20 МБ сжатого) Иногда я получаю исключение при создании нового растрового изображения. Все, что выше этого, каждый раз ошибается одинаково.
Источник-30 МБ текстового файла, сжатого примерно до 15 МБ
Высота Изображения 2943
Ширина Изображения 5232
пиксели = 15397776
никакая ошибка
Источник-40 МБ текстового файла, сжатого примерно до 20 МБ
ImageHeight 3402 Целое Число
ImageWidth 6048 Целое Число
пиксели = 20575296
Нарушающая Линия - MainBitmap = Новое Растровое Изображение(ImageWidth, ImageHeight)
Система.ArgumentException был необработан
Значение HRESULT=-2147024809
Сообщение=недопустимый параметр.
исходная система.Рисование
Пользователь начинает с открытия текстового файла или создания / ввода пароля. После того, как оба они сделаны, пользователь выбирает "шифровать", и процесс начинается:
1 - содержимое текстового файла (хранящегося в виде массива байтов) сжимается в другой массив байтов с помощью сжатия GZIP или Deflate.
2 - Размер сжатого массива преобразуется в ширину и высоту растрового изображения с приблизительными соотношениями сторон по выбору пользователя (16:9, 4:3, 1:1), и создается пустое растровое изображение
3 - для каждого возможного байта в массиве (0..255) генерируется несколько уникальных цветов и добавляется в список (цвета), используя SHA-хэш символа и пароль, сцепленные причудливым образом.
4 - для каждого байта в содержимом текстового файла пиксель растрового изображения устанавливается в один из цветов, соответствующих этому байту.
5 - Когда закончите, пользователь может сохранить растровое изображение в виде файла .png (без потерь)
На втором этапе происходит исключение
Растровое изображение объявляется на уровне формы просто как растровое изображение, а затем устанавливается как новое растровое изображение, как только пользователь решает зашифровать кучу текста. В этот момент детали профиля памяти показывают массив 50 МБ как 52 МБ, а сжатый-как 25. Использование памяти резко возрастает (900 МБ), когда текст загружается, а затем снова опускается до 80 Мб или около того и остается там. Он поднимается на небольшую величину непосредственно перед исключением.
"Исходный" массив - это размер текстового файла, использующего файл.ReadAllBytes.
Сжатый массив составляет примерно половину размера "исходного" массива.
У меня есть два словаря,каждый из которых содержит 1024 записи (целое число,цвет) и (цвет, целое число)
Что я уже пробовал:
<pre lang="text">Я преобразовал приложение Forms в консольное приложение, и оно проносится через исходный файл размером от 100 до 200 МБ без каких-либо проблем.
Я также переписал часть шифрования в приложении Forms и избавился от каждого "большого объекта" (кроме растрового изображения)
Входной файл никогда не считывается в массив или строку. Входной файл-это сжатый файл для преобразования в сжатый файл. Если входной файл меньше 256K, он отображается, в противном случае отображаются только первая и последняя строки с помощью streamreader для чтения строк, опять же, никаких больших строк.
Этот сжатый файл непосредственно сохраняется на диске как временный файл, он никогда не попадает в массив.
При шифровании один байт за раз считывается двоичным кодом из сжатого файла и обрабатывается.
Протестирована и подтверждена работа (путем расшифровки) на чем угодно вплоть до 40 Мбайт включительно.
При 50 Мбайт одна и та же ошибка возникает каждый раз при установке размера растрового изображения (недопустимый параметр).
Это не постепенная ошибка, то есть я могу попробовать файл размером 50 МБ и получить ошибку, затем попробовать файлы размером 1,2,5,10,20 и т. д. без ошибок, а затем снова 50, какие ошибки.
Вот точный код, за исключением того, что я заменил 2 словаря одним списком, теперь он обрабатывает 50 МБ, но не 60. На моей машине x64 он обрабатывает 200, но показывает 900 МБ используемой памяти.
Option Strict On Imports System.IO Imports System.IO.Compression Imports System.Security.Cryptography Imports System.Text Public Class Form1 Private WithEvents TxtStatus As New TextBox Private WithEvents BtnTest As New Button Private WithEvents TxtEncryptKey As New TextBox Private Label1 As New Label Private WithEvents BtnLoadFile As New Button Private TxtPlain As New RichTextBox Private RNG As New Random Private CompressedFileName As String Private CompressedLength As Long Private InputFileName As String Private InputLength As Long Private ColorList As New List(Of Color) Private MainBitmap As Bitmap Private EncryptKey As String Private ImageWidth As Integer Private ImageHeight As Integer Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load AutoScaleMode = AutoScaleMode.Font ClientSize = New Size(604, 270) MinimumSize = Size With TxtStatus .Anchor = CType(((AnchorStyles.Bottom Or AnchorStyles.Left) Or AnchorStyles.Right), AnchorStyles) .Location = New Point(4, 224) .Multiline = True .Size = New Size(594, 37) End With With BtnTest .Location = New Point(206, 188) .Size = New Size(120, 23) .Text = "Test" End With With TxtEncryptKey .Anchor = CType(((AnchorStyles.Top Or AnchorStyles.Left) Or AnchorStyles.Right), AnchorStyles) .Location = New Point(80, 131) .Size = New Size(518, 22) End With With Label1 .AutoSize = True .Location = New Point(3, 134) .Size = New Size(70, 14) .Text = "Password:" End With With BtnLoadFile .Location = New Point(80, 188) .Size = New Size(120, 23) .Text = "Load A File" End With With TxtPlain .Anchor = CType(((AnchorStyles.Top Or AnchorStyles.Left) Or AnchorStyles.Right), AnchorStyles) .Location = New Point(4, 2) .Size = New Size(594, 123) End With With Me .Controls.Add(TxtPlain) .Controls.Add(BtnLoadFile) .Controls.Add(BtnTest) .Controls.Add(TxtStatus) .Controls.Add(Label1) .Controls.Add(TxtEncryptKey) .Font = New Font("Consolas", 9.0!, FontStyle.Regular, GraphicsUnit.Point, CType(0, Byte)) .Text = "ONLY FOR TESTING " & " " & Application.ProductVersion & " TESTING ONLY" End With End Sub Private Function GetSHA1Color(ByVal Input As String) As Color Dim SHA1Hasher As New SHA1Managed() Dim Hash As Byte() = SHA1Hasher.ComputeHash(Encoding.Default.GetBytes(Input)) Return Color.FromArgb(255, Hash(0), Hash(1), Hash(2)) End Function Private Function FillColorList(EncryptKey As String) As Boolean ColorList.Clear() Dim ColorToAdd As Color = Nothing For CharacterValue As Integer = 0 To 255 ColorToAdd = GetSHA1Color(EncryptKey & Chr(CharacterValue)) If ColorList.Contains(ColorToAdd) Then Return False Else ColorList.Add(ColorToAdd) End If Next Return True End Function Private Sub CompressFileToFile(InFile As String, Outfile As String) Dim InFileInfo As FileInfo = New FileInfo(InFile) Dim InFileLength As Long = InFileInfo.Length Dim Bytesread As Long = 0 Dim BytesRemaining As Long = InFileLength Dim BufferSize As Long = InFileLength \ 100 Dim BytesToRead As Long = BufferSize Dim buffer(CInt(BufferSize - 1)) As Byte Dim OutFileinfo As New FileInfo(Outfile) Me.Cursor = Cursors.WaitCursor Try Using originalFileStream As FileStream = InFileInfo.OpenRead() Using compressedFileStream As FileStream = File.Create(Outfile) Using compressionStream As New GZipStream(compressedFileStream, CompressionMode.Compress) Do While BytesRemaining > 0 originalFileStream.Read(buffer, 0, CInt(BufferSize)) compressionStream.Write(buffer, 0, CInt(BufferSize)) Bytesread += BufferSize TxtStatus.Text = "Compressing - " & ((Bytesread * 100) \ InFileLength).ToString("000") & "%" TxtStatus.Refresh() BytesRemaining -= BufferSize If BytesRemaining < BufferSize Then BufferSize = BytesRemaining Loop End Using End Using End Using Catch ex As Exception TxtStatus.Text = "Error Compressing Input File " & ex.Message If File.Exists(CompressedFileName) Then File.Delete(CompressedFileName) End Try Me.Cursor = Cursors.Default End Sub Private Sub CalcSize(InputString As String) CompressedFileName = Path.GetTempFileName CompressFileToFile(InputString, CompressedFileName) If Not File.Exists(CompressedFileName) Then InputFileName = "" CompressedFileName = "" MainBitmap = Nothing Exit Sub End If Dim FileLength As Integer = CInt(My.Computer.FileSystem.GetFileInfo(CompressedFileName).Length) CompressedLength = FileLength Dim Temp As Long = 0 For N As Integer = 1 To 100000 ImageWidth = N ImageHeight = N Temp = ImageWidth * ImageHeight If Temp > FileLength Then Exit For End If Next TxtStatus.Text = "Compressed Length is " & FileLength.ToString & ", the bitmap will be " & ImageWidth.ToString & " by " & ImageHeight.ToString & vbNewLine If Not MainBitmap Is Nothing Then MainBitmap.Dispose() End If Try MainBitmap = New Bitmap(ImageWidth, ImageHeight, Imaging.PixelFormat.Format32bppArgb) Catch ex As Exception TxtStatus.Text = ImageWidth.ToString & "x" & ImageHeight.ToString & " Bitmap Creation Failed - " & ex.Message & vbNewLine & ex.StackTrace MainBitmap = Nothing End Try End Sub Private Sub BtnTest_Click(sender As System.Object, e As System.EventArgs) Handles BtnTest.Click If EncryptKey = "" Then TxtStatus.Text = "A Password is required." Exit Sub End If If Not File.Exists(InputFileName) OrElse My.Computer.FileSystem.GetFileInfo(InputFileName).Length = 0 Then TxtStatus.Text = "Some Plain text is required" Exit Sub End If If Not FillColorList(EncryptKey) Then MessageBox.Show("The Password you chose : " & EncryptKey & vbNewLine & "Cannot be used for testing." & vbNewLine & "Choose a different password.", "Cannot test with this password", MessageBoxButtons.OK, MessageBoxIcon.Error) Exit Sub End If TxtStatus.Text = "Preparing .........." TxtStatus.Refresh() CalcSize(InputFileName) If File.Exists(CompressedFileName) Then File.Delete(CompressedFileName) If MainBitmap Is Nothing Then Exit Sub Else TxtStatus.Text = ImageWidth.ToString & "x" & ImageHeight.ToString & " Bitmap Creation Succeeded" End If End Sub Private Sub TxtKey_TextChanged(sender As System.Object, e As System.EventArgs) Handles TxtEncryptKey.TextChanged EncryptKey = TxtEncryptKey.Text BtnTest.Enabled = EncryptKey.Length > 0 End Sub Private Sub BtnLoadFile_Click(sender As System.Object, e As System.EventArgs) Handles BtnLoadFile.Click Dim FileSize As Long = 0 Dim TempString As String = "" Using OFD As New OpenFileDialog OFD.Title = "Find the text file you wish to encrypt" OFD.Filter = "Text Files (*.txt)|*.txt" If OFD.ShowDialog = Windows.Forms.DialogResult.OK Then InputFileName = OFD.FileName FileSize = My.Computer.FileSystem.GetFileInfo(InputFileName).Length InputLength = FileSize Try If FileSize > 64 * 1024 Then Dim firstLine As String = Nothing ' or String.Empty Dim lastLine As String = Nothing ' same here Using reader As New StreamReader(InputFileName) If Not reader.EndOfStream Then firstLine = reader.ReadLine Do Until reader.EndOfStream lastLine = reader.ReadLine Loop End Using TxtPlain.Text = firstLine & vbNewLine & vbNewLine & "Text Redacted" & vbNewLine & vbNewLine & lastLine Else TxtPlain.Text = File.ReadAllText(InputFileName, Encoding.Default) End If TxtStatus.Text = "Read " & FileSize.ToString & " Characters" Catch ex As Exception TxtStatus.Text = "Error reading file - " & ex.Message InputFileName = "" End Try End If End Using End Sub End Class