zomalaja Ответов: 1

Проблемы с памятью создание растрового изображения в 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

1 Ответов

Рейтинг:
1

Dave Kreskowiak

Проще говоря, вы не можете сделать растровый объект таким большим, особенно если вы забыли вызвать Dispose для объекта, который требует этого, когда вы закончите их использовать. Это особенно верно для рисования объектов.

Прочитай этот[^],


zomalaja

Как я уже сказал, консольная версия не имеет никаких проблем вообще. Растровое изображение никогда нигде не отображается, оно существует только для того, чтобы быть сохраненным в виде файла png. Я не совсем понимаю, что вы имеете в виду, вызывая dispose для объекта, который этого требует. Какие типы объектов это могут быть, кроме picturebox или чего-то еще, где растровое изображение отображается/окрашивается ?
Ссылка, которую вы предоставили, говорит о растровых изображениях размером 21000x21000, на несколько порядков больше, чем то, что я пытаюсь сделать.

Dave Kreskowiak

Растровые изображения также должны быть удалены. Почти все объекты GDI должны быть утилизированы. Если вы создаете растровое изображение за растровым изображением и не избавляетесь от них, когда закончите с ними, ваше приложение утекает ресурсы и память.

Суть этой связи заключалась не в размере, а в том, как работают растровые объекты в GDI и какие ограничения накладывает память.

Dave Kreskowiak

Это мило и все такое, и совершенно бесполезно. Ваш тестовый код бессмыслен, поскольку он не делает то, что делает ваше приложение. Мы либо видим настоящий код, либо вы сами по себе.

Что вы пропустили в ссылке, так это то, что фрагментация памяти также может повлиять на ограничения размера изображений, которые вы можете создать с помощью GDI.

Dave Kreskowiak

Если переменная, которая должна содержать растровое изображение, равна нулю, то выбрасывать нечего. Он не был создан.

Но если вы снова и снова используете одну и ту же переменную для хранения новых растровых изображений, старое растровое изображение должно быть удалено до того, как вы создадите новое, чтобы заменить его.

То, что вы делаете, - это фрагментация памяти до такой степени, что вы не можете создать достаточно большое растровое изображение. Растровое изображение-это не единственный крупный объект, который вы создаете. Если Вы читаете весь файл в память, прежде чем что-то с ним делать, вы создаете большой объект. Если вы храните содержимое зашифрованного файла в памяти, вы создаете еще один большой объект. Если вы используете список и продолжаете добавлять и добавлять к нему, вы создаете большой объект за большим объектом, потому что внутренний массив в списке должен быть увеличен, а содержимое скопировано в новый массив.

Кроме того, что происходит с созданием растрового изображения в первую очередь? Что вы можете показать в нем, что имеет какое-либо отношение к сжатию текстового файла?

Dave Kreskowiak

Там нет возможности для вложений или изображений. Я также не загружаю ненадежный код из ненадежного источника. Вы вставляете соответствующие фрагменты кода в исходное сообщение.

zomalaja

Спасибо ни за что - надеюсь, кто-то увидит мою просьбу и удалит эту тему.

zomalaja

Attn любой модератор - если это возможно, пожалуйста, удалите всю эту тему, это была полная трата моего времени, и ни одна из информации, которую я получил, не является ни малейшей полезной.

Заранее спасибо.