Dealing with a memory leak when cropping images in vb.net - vb.net

I keep getting a system out of memory error when I break an image up into sub images. There's nothing too fancy going on, just splitting an image into a bunch of smaller images. Here's my code:
Dim counterrr As Integer = 0
Dim sorucedir As String
Dim tardir As String
sorucedir = "C:\somedir\"
tardir = "C:\otherdir\"
Dim di2 As New DirectoryInfo(sorucedir)
Dim fiArr2 As FileInfo() = di2.GetFiles()
Dim fri2 As FileInfo
Dim hh As Integer
Dim ww As Integer
hh = 995 'height of source images
ww = 1080 'width of source images
Dim sizestepX As Integer = 180
Dim stepsizeY As Integer = 239
For i = 0 To ww - 1 Step sizestepX
For j = 0 To hh - 1 Step stepsizeY
For Each fri2 In fiArr2
Dim BM2 As Bitmap
BM2 = Image.FromFile(fri2.FullName)
''Threading.Thread.Sleep(3000)
'bm2 = Image.FromFile(fri2.FullName)
Dim BM3 As Bitmap
Dim rect As New Rectangle(i, j, sizestepX, stepsizeY)
BM3 = BM2.Clone(rect, Imaging.PixelFormat.DontCare)
counterrr += 1
Dim ss As String
ss = tardir & counterrr & ".png"
BM3.Save(ss, Imaging.ImageFormat.Png)
BM3 = Nothing
Next
Next
Next
The error comes up after creating as few as 6 images (occurs when creating the 7th image). The line where the error is thrown is this:
BM3 = BM2.Clone(rect, Imaging.PixelFormat.DontCare)
How can I modify my code so they objects are correctly disposed (?) of so they don't leak memory.

The Bitmap class, along with many of the other GDI+ classes implement IDisposable because they hold onto underlying GDI object handles which need to be released. Each process is limited by the OS to a maximum number of GDI objects, usually 10,000. You can view the total number of active GDI objects per process in the Windows task manager. To fix your problem, you need to either manually call the Dispose method on each Bitamp object you create, before it goes out of scope, or you need to wrap each Bitmap variable declaration in a Using block (so it will call Dispose for you). For instance:
Using BM2 As Bitmap = Image.FromFile(fri2.FullName)
Dim rect As New Rectangle(i, j, sizestepX, stepsizeY)
Using BM3 As Bitmap = BM2.Clone(rect, Imaging.PixelFormat.DontCare)
counterrr += 1
Dim ss As String
ss = tardir & counterrr & ".png"
BM3.Save(ss, Imaging.ImageFormat.Png)
End Using
End Using
Note: there's no point in setting the BM3 variable to Nothing at the end, like you're doing. That has no bearing on anything.

Related

Image.Save() throws GDI+ exception for image derived from bitmap

I have two classes: ChartObject & GrowthChartPane.
ChartObject has a method, GetChart(), that converts a bitmap to a Drawing.Image that is returned.
Public Function GetChart() As Drawing.Image
Dim chartImage As Drawing.Image
Dim gr As Graphics
Dim brush As Brush = Brushes.Blue
If m_bIsBoy Then
brush = Brushes.Red
End If
Using fs As New FileStream(m_sChartImageFileName, FileMode.Open, FileAccess.Read)
chartImage = Bitmap.FromStream(fs)
gr = Graphics.FromImage(chartImage)
End Using
For Each Point As PointObject In m_lstUpperPoints
If Not Point.IsVisible Then Continue For
Dim x As Integer = m_objUpperHorizontalAxis.ValueToPoint(Point.HorizontalValue)
Dim y As Integer = m_objUpperVerticalAxis.ValueToPoint(Point.VerticalValue)
gr.FillEllipse(brush, New RectangleF(x - 4, y - 4, 8, 8))
Next
For Each Point As PointObject In m_lstLowerPoints
If Not Point.IsVisible Then Continue For
Dim x As Integer = m_objLowerHorizontalAxis.ValueToPoint(Point.HorizontalValue)
Dim y As Integer = m_objLowerVerticalAxis.ValueToPoint(Point.VerticalValue)
gr.FillEllipse(brush, New RectangleF(x - 4, y - 4, 8, 8))
Next
DrawHeaderAndTable(gr)
Return chartImage
End Function
GrowthChartPane, when it is loading, makes a call to ChartObject.GetChart() to instantiate a Drawing.Image object. When trying to Save the image that is returned from growth chart, the exception occurs. The method is pretty big, but here is a snippet from the end where the exception is.
Dim fn As String = PediatricGrowthChartsImageHandler.GetPGCImagePath(CurrentPatient.EntityID, m_iChartTypeId)
If Not IsNothing(customDrowingChart) Then
Dim chartImage As Drawing.Image = customDrowingChart.GetChart()
hdnImgChartH.Value = chartImage.Height.ToString 'test
hdnImgChartW.Value = chartImage.Width.ToString 'test
chartImage.Save(fn, System.Drawing.Imaging.ImageFormat.Png)
chartImage.Dispose()
imgChart.ImageUrl = String.Format("PediatricGrowthChartsImageHandler.axd?PatientID={0}&PGCTypeID={1}&rnd={2}", CurrentPatient.EntityID, m_iChartTypeId, New Random().NextDouble().ToString())
Else
Chart1.SaveImage(fn, ChartImageFormat.Png)
End If
End If
If Not IsNothing(DataToBeFilled) Then DataToBeFilled.dispose()
End Sub
I have confirmed that the file does NOT exist, yet exception is still thrown on Image.Save()
I've done some research on this topic & there are so many different solutions out there, but that brought me to a concern about the stream being open.
I've read that the stream needs to be open to Save an image and that you can directly pass a stream into the Save() method, but I 'm curious about how that interaction goes between the two classes.

Bit cost per pixel of PNG file

In the PNG file format wikipedia page I saw this conversion of an image to a representation of the cost of bits per pixel (red=expensive, blue=cheap).
I was wondering if it is doable to program that kind of converter.
At the moment I don't know where to look up information about that so I decided to ask here with the little VB.NET code that I have made:
Dim MyBitmap As Bitmap = New Bitmap(filename:="C:\Original.png")
Dim MyBinaryReader As BinaryReader = New BinaryReader(File.OpenRead(path:="C:\Original.png"))
Dim MyBytes() As Byte = MyBinaryReader.ReadBytes(count:=Convert.ToInt32(File.OpenRead(path:="C:\Original.png").Length))
Dim MyString As String = ""
For Each MyByte As Byte In MyBytes
MyString = MyString & Convert.ToString(value:=MyByte, toBase:=2).PadLeft(totalWidth:=8, paddingChar:="0"c)
Next
Debug.WriteLine(MyString)
'For MyHeight As Integer = 0 To MyBitmap.Height - 1
' For MyHeight As Integer = 0 To MyBitmap.Height - 1
' MyBitmap.SetPixel(x:=MyWidth, y:=MyHeight, color:=MyBitmap.GetPixel(x:=MyWidth, y:=MyHeight))
' Next
'Next
'MyBitmap.Save(filename:="C:\New.png")

VB.NET Dispose Bitmap in RAM wont work

first of all i am relative new to Visual Basic.NET and i stuck on an problem here.
I started to code a screen2gif recorder. In main purpose it works. But if i record more than 15 to 20 seconds my ram overloads and exceed the 3,5 Gb limit for x32bit applications. The problem is that the bitmaps i create stack over and over.
Private Function getBitmap(ByVal pCtrl As Control) As Bitmap
Dim myBmp As Bitmap
If myBmp IsNot Nothing Then
myBmp.Dispose()
End If
myBmp = New Bitmap(pCtrl.Width, pCtrl.Height)
Dim g As Graphics = Graphics.FromImage(myBmp)
Dim p As New Point(pCtrl.Parent.Width - pCtrl.Parent.ClientRectangle.Width - 4, pCtrl.Parent.Height - pCtrl.Parent.ClientRectangle.Height - 4)
g.CopyFromScreen(pCtrl.Parent.Location + pCtrl.Location + p, Point.Empty, myBmp.Size)
Dim LocalMousePosition As Point
LocalMousePosition = panelTranspacrency.PointToClient(Cursor.Position)
Cursor.Draw(g, New Rectangle(New Point(LocalMousePosition.X, LocalMousePosition.Y), Cursor.Size))
Return myBmp
myBmp.Dispose()
g.Dispose()
End Function
Private Sub tmrWork_Tick(sender As Object, e As EventArgs) Handles tmrWork.Tick
counter += 1
Dim bm As Bitmap
bm = getBitmap(Me.panelTranspacrency)
bm.Save(My.Settings.outputpath & "\temp\" & counter & ".png", Drawing.Imaging.ImageFormat.Png)
bm.Dispose()
End Sub
So this is my code to create the bitmaps and save them to disk.
I mention that i used the .Dispose command but the ram wont free.
Please take a look and give me a hint. Thanks in advance.
Change
Dim g As Graphics = Graphics.FromImage(myBmp)
to
Using g As Graphics = Graphics.FromImage(myBmp)
and put End Using after your Return myBmp. And you should do the same with
Using bm As Bitmap = getBitmap(Me.panelTranspacrency)
And remove all your explicit .Dispose calls as well.
Docs
https://learn.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/using-statement

Using Logo to draw sound

I'm using Logo right now and i'm making a project and basically i want to turn your recorded voice into something visual, only problem is when i go to find code it re that works it requires 1: A picture box and 2: to manually grab the sound .wav file and place it. I already made code to record my voice and to make it into a .Wav file and i already have code to visualize it, just when i run it it appears as a thick square of lines rather than the example i shown. Note: I'm not drawing into a picturebox, i'm drawing directly into the Form by using g.drawline(bleh,bleh,bleh,bleh).
(Example: http://static1.1.sqspcdn.com/static/f/335152/16812948/1330286658510/76_dsc3616.jpeg?token=R1zPNnr9PAoB3WvnDxfFFFvzkMw%3D )
The code im trying to run:
Public Sub DrawSound(x As Integer, y As Integer)
Dim samplez As New List(Of Short)
Dim maxamount As Short
Dim pic As New Bitmap(x, y)
Dim ratio As Integer = (samplez.Count - 1) / (y - 1) 'If there are 10000 samples and 200 pixels, this would be every 50th sample is shown
Dim halfpic As Integer = (x / 2) 'Simply half the height of the picturebox
GC.Collect()
Dim wavefile() As Byte = IO.File.ReadAllBytes("C:\Users\" & Environ$("Username") & "\Documents\Sounds\Mic.wav")
GC.Collect()
Dim memstream As New IO.MemoryStream(wavefile)
Dim binreader As New IO.BinaryReader(memstream)
Dim ChunkID As Integer = binreader.ReadInt32()
Dim filesize As Integer = binreader.ReadInt32()
Dim rifftype As Integer = binreader.ReadInt32()
Dim fmtID As Integer = binreader.ReadInt32()
Dim fmtsize As Integer = binreader.ReadInt32()
Dim fmtcode As Integer = binreader.ReadInt16()
Dim channels As Integer = binreader.ReadInt16()
Dim samplerate As Integer = binreader.ReadInt32()
Dim fmtAvgBPS As Integer = binreader.ReadInt32()
Dim fmtblockalign As Integer = binreader.ReadInt16()
Dim bitdepth As Integer = binreader.ReadInt16()
If fmtsize = 18 Then
Dim fmtextrasize As Integer = binreader.ReadInt16()
binreader.ReadBytes(fmtextrasize)
End If
Dim DataID As Integer = binreader.ReadInt32()
Dim DataSize As Integer = binreader.ReadInt32()
samplez.Clear()
For i = 0 To (DataSize - 3) / 2
samplez.Add(binreader.ReadInt16())
If samplez(samplez.Count - 1) > maxamount Then 'Using this for the pic
maxamount = samplez(samplez.Count - 1)
End If
Next
For i = 1 To x - 10 Step 2 'Steping 2 because in one go, we do 2 samples
Dim leftdata As Integer = Math.Abs(samplez(i * ratio)) 'Grabbing that N-th sample to display. Using Absolute to show them one direction
Dim leftpercent As Single = leftdata / (maxamount * 2) 'This breaks it down to something like 0.0 to 1.0. Multiplying by 2 to make it half.
Dim leftpicheight As Integer = leftpercent * x 'So when the percent is tied to the height, its only a percent of the height
g.DrawLine(Pens.LimeGreen, i, halfpic, i, leftpicheight + halfpic) 'Draw dat! The half pic puts it in the center
Dim rightdata As Integer = Math.Abs(samplez((i + 1) * ratio)) 'Same thing except we're grabbing i + 1 because we'd skip it because of the 'step 2' on the for statement
Dim rightpercent As Single = -rightdata / (maxamount * 2) 'put a negative infront of data so it goes down.
Dim rightpicheight As Integer = rightpercent * x
g.DrawLine(Pens.Blue, i, halfpic, i, rightpicheight + halfpic)
Next
End Sub
X and Y is the middle of the form. And i also would link where i got the code but i forgot where and also, i modified it in attempt to run it directly into he form rather than a picturebox. It worked sorta haha (And there is so many unused dims but all i know is, once i remove one none of the code works haha) So could anyone help?

Reading a file bug in VB.NET?

The way this file works is there is a null buffer, then a user check sum then a byte that gives you the user name letter count, then a byte for how many bytes to skip to the next user and a byte for which user file the user keeps their settings in.
the loop with the usersm variable in the IF statement sets up the whole file stream for extraction. However with almost the exact same code the else clause specifically the str.Read(xnl, 0, usn - 1) in the else code appears to be reading the very beginning of the file despite the position of the filestream being set earlier, anyone know whats happening here?
this is in vb2005
Private Sub readusersdata(ByVal userdatafile As String)
ListView1.BeginUpdate()
ListView1.Items.Clear()
Using snxl As IO.Stream = IO.File.Open(userdatafile, IO.FileMode.Open)
Using str As New IO.StreamReader(snxl)
str.BaseStream.Position = 4
Dim usersm As Integer = str.BaseStream.ReadByte()
Dim users As Integer = usersm
While users > 0
If usersm = users Then
Dim trailtouser As Integer = 0
str.BaseStream.Position = 6
Dim ust As Integer = str.BaseStream.ReadByte()
str.BaseStream.Position = 8
Dim snb(ust - 1) As Char
str.ReadBlock(snb, 0, ust)
Dim bst = New String(snb)
If usersm = 1 Then
str.BaseStream.Position = 16
Else
str.BaseStream.Position = 15
End If
cLVN(ListView1, bst, str.BaseStream.ReadByte)
str.BaseStream.Position = 8 + snb.Length
str.BaseStream.Position += str.BaseStream.ReadByte + 1
Else
Dim usn As Integer = str.BaseStream.ReadByte
str.BaseStream.Position += 2
Dim chrpos As Integer = str.BaseStream.Position
Dim xnl(usn - 1) As Char
str.Read(xnl, 0, usn - 1)
Dim skpbyte As Integer = str.BaseStream.ReadByte
str.BaseStream.Position += 3
Dim udata As Integer = str.BaseStream.ReadByte
End If
users -= 1
End While
End Using
End Using
ListView1.EndUpdate()
End Sub
When you change the position of the underlying stream, the StreamReader doesn't know you've done that. If it's previously read "too much" data (deliberately, for the sake of efficiency - it tries to avoid doing lots of little reads on the underlying stream) then it will have buffered data that it'll use instead of talking directly to the repositioned stream. You need to call StreamReader.DiscardBufferedData after repositioning the stream to avoid that.