Bitmap read, write & compare - vb.net

A) When I write a BMP file & then read it back into another bitmap object the objects appear different. Looks like Horizontal & Vertical Resolutions are changed.
B) When I make the bitmap object resolutions the same the bitmap objects still appear different despite all attributes appearing the same.
C) If I read the same BMP file twice into two different bitmap objects the objects appear different.
Eventually I want to clone small sections of a larger bitmap & ascertain whether I already have a file for the smaller section by comparing against the contents of each known previously generated file in turn. That is, by reading each file as a bitmap I want to be able to compare against the small section bitmap.
I will eventually be placing a checksum in each file's name or attributes for quicker exclusion but will still need to compare each file contents of those who "survive the exclusion" to prevent different files with the same checksum issue.
When run, the code below shows the issues.
Private Sub btnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click
Dim sFileName As String = "c:\Temp\Red.bmp"
' Create red coloured bitmap
Dim oBitMap1 As New Bitmap(200, 100)
Dim oGraphics = Graphics.FromImage(oBitMap1)
oGraphics.FillRectangle(Brushes.Red, 0, 0, 200, 100)
Dim oBitMapA As New Bitmap(200, 100)
oBitMapA = oBitMap1
Debug.Print("oBitMap1 Is oBitMapA = " & CStr(oBitMap1 Is oBitMapA))
' Save the bitmap
oBitMap1.Save(sFileName, Imaging.ImageFormat.Bmp)
' Read the "same" details back into oBitMap2 & BitMap3
Dim oBitMap2 = New Bitmap(sFileName)
Dim oBitMap3 = New Bitmap(sFileName)
'Dim oBitMap2 As Bitmap = Image.FromFile(sFileName)
'Dim oBitMap3 As Bitmap = Image.FromFile(sFileName)
' Show what's what
Debug.Print("Show whether BitMaps are the same (pre changes)")
Debug.Print("oBitMap1 Is oBitMap2 = " & CStr(oBitMap1 Is oBitMap2))
Debug.Print("oBitMap2 Is oBitMap3 = " & CStr(oBitMap2 Is oBitMap3))
Debug.Print("Show resolutions (pre any changes)")
Debug.Print("oBitMap1.Resolution = " & Format(oBitMap1.HorizontalResolution, "##0.###") & "," & Format(oBitMap1.VerticalResolution, "##0.###"))
Debug.Print("oBitMap2.Resolution = " & Format(oBitMap2.HorizontalResolution, "##0.###") & "," & Format(oBitMap2.VerticalResolution, "##0.###"))
Debug.Print("oBitMap3.Resolution = " & Format(oBitMap3.HorizontalResolution, "##0.###") & "," & Format(oBitMap3.VerticalResolution, "##0.###"))
oBitMap2.SetResolution(96, 96)
oBitMap3.SetResolution(96, 96)
Debug.Print("Show resolutions (post changes)")
Debug.Print("oBitMap1.Resolution = " & Format(oBitMap1.HorizontalResolution, "##0.###") & "," & Format(oBitMap1.VerticalResolution, "##0.###"))
Debug.Print("oBitMap2.Resolution = " & Format(oBitMap2.HorizontalResolution, "##0.###") & "," & Format(oBitMap2.VerticalResolution, "##0.###"))
Debug.Print("oBitMap3.Resolution = " & Format(oBitMap3.HorizontalResolution, "##0.###") & "," & Format(oBitMap3.VerticalResolution, "##0.###"))
Debug.Print("Show whether BitMaps are the same (post changes)")
Debug.Print("oBitMap1 Is oBitMap1 = " & CStr(oBitMap1 Is oBitMap1))
Debug.Print("oBitMap1 Is oBitMap2 = " & CStr(oBitMap1 Is oBitMap2))
Debug.Print("oBitMap2 Is oBitMap3 = " & CStr(oBitMap2 Is oBitMap3))
End Sub
Using descriptions above
A) I'm expecting oBitMap2 read from the file created from oBitMap1 to be the same as oBitMap1 but the resolutions are different.
B) When I set the oBitMap2 resolutions to be the same as oBitMap1 I'm expecting oBitMap1 & oBitmap2 to be the same but they're different.
C) When I read the same file twice I'm expecting the two bitmaps created (oBitMap2 & oBitMap3) to be the same but they're different.
I am wondering whether I've misunderstood the "Is" compare operator or the bitmaps have "hidden" attributes that don't show when I "print" them in the debugger as all the shown attributes appear to be the same.

Related

Processing TIF images with Marshal.Copy ends in a Fatal Error

I have a function that inserts one image into another byte-wise:
Public Function InsertPIP(ByRef BmpSrc As Bitmap, ByRef BmpTgt As Bitmap, InsertPoint As Point, Optional bpp As Int16 = 1) As Bitmap
If BmpSrc.Width + InsertPoint.X <= BmpTgt.Width And BmpSrc.Height + InsertPoint.Y <= BmpTgt.Height Then
Try
Dim DataSrc As Imaging.BitmapData = BmpSrc.LockBits(New Rectangle(0, 0, BmpSrc.Width, BmpSrc.Height), Imaging.ImageLockMode.[WriteOnly], Imaging.PixelFormat.Format1bppIndexed)
Dim BytesSrc As Byte() = New Byte(DataSrc.Height * DataSrc.Stride) {}
If VerboseLevel >= 3 Then LW.WriteLine("InsertPIP: Stride=" & DataSrc.Stride.ToString & " Scan=" & DataSrc.Scan0.ToInt64 & " DataSRC.Size=" & DataSrc.Width & "x" & DataSrc.Height & " BytesSRC.len=" & BytesSrc.Length.ToString & " Height*Stride=" & (DataSrc.Height * DataSrc.Stride).ToString)
Marshal.Copy(DataSrc.Scan0, BytesSrc, 0, BytesSrc.Length)
'^^^^^^^^^^^^ ERROR OCURS HERE ^^^^^^^^^^^^
Dim DataTgt As Imaging.BitmapData = BmpTgt.LockBits(New Rectangle(0, 0, BmpTgt.Width, BmpTgt.Height), Imaging.ImageLockMode.[WriteOnly], Imaging.PixelFormat.Format1bppIndexed)
Dim BytesTgt As Byte() = New Byte(DataTgt.Height * DataTgt.Stride - 1) {}
Marshal.Copy(DataTgt.Scan0, BytesTgt, 0, BytesTgt.Length)
BytesTgt = InsertPictureIntoPicture(BytesSrc, BytesTgt, BmpSrc.Width, 1, New Rectangle(InsertPoint.X, InsertPoint.Y, BmpSrc.Width, BmpSrc.Height), DataSrc.Stride * 8 / bpp, DataTgt.Stride * 8 / bpp)
Marshal.Copy(BytesTgt, 0, DataTgt.Scan0, BytesTgt.Length)
BmpSrc.UnlockBits(DataSrc)
BmpTgt.UnlockBits(DataTgt)
Catch ex As Exception
If VerboseLevel >= 3 Then LW.WriteLine("InsertPIP ERROR: " & ex.Message & vbCrLf)
End Try
Return BmpTgt
Else
LW.WriteLine("WARNING: Inserting bitmap into bitmap: Source doesn't fit the target. BmpSrc=[" & BmpSrc.Width & "," & BmpSrc.Height & "] BmpTgt=[" & BmpTgt.Width & "," & BmpTgt.Height & "] pt=[" & InsertPoint.X & "," & InsertPoint.Y & "]")
Return BmpTgt
End If
End Function
It's been working for more than a year fine in 99+% cases. But some rare images throw cause a fatal error on line Marshal.Copy(...) ("Fatal Error Detected - unable to continue. Unhandled operating system exception: e0434352"). I am trying to find the cause of the problem but:
debugger seems to me useless, it just says System.AccessViolationException and System.Reflection.TargetInvocationException with no other details
try-catch does not catch the fatal error
trying to see the print the inputs didn't help me:
InsertPIP: Stride=512 Scan=2562876506112 DataSRC.Size=4088x712 BytesSRC.len=364545 Height*Stride=364544
EDIT: I tried to isolate it into standalone application and debug there and no I get little bit more detailed exception message (and what I expected):
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'
I generate all the TIF images, they're generated with the same methods and they have all `PixelFormat.Format1bppIndexed`.
Affected Image:
I haven't been able to find much on the internet, suggestions apprectiated. If I can improve my question, please let me know how.
Link to the original TIF image: https://drive.google.com/file/d/1xTIQi5M9UxK7A98992W3PUNMOmODP8p2/view?usp=sharing

PDFsharp: How to find the Size dimensions of all the pages in a PDF file?

I am using PDFsharp, a great tool for working with PDFs.
I am writing an application in VB.net to work with PDFs for the printing industry. I need to know how to find out the dimensions of each page of the PDF.
Loop through all pages in the PDF and query the page size for each page.
Use the Pages property of the PdfDocument object.
Function GetPDFMetaData(ByRef pSourceFile As OpenFileDialog)
Dim lpdfDocument As PdfDocument = PdfReader.Open(pSourceFile.FileName, PdfDocumentOpenMode.Import)
Dim lpdfpage As PdfPage
Dim Text As String = ""
Dim Width As Integer
Dim Height As Integer
For idx As Integer = 0 To lpdfDocument.PageCount - 1
lpdfpage = lpdfDocument.Pages(idx)
Width = lpdfpage.Width.Millimeter
Height = lpdfpage.Height.Millimeter
Text = Text & vbCrLf & "Page: (" & idx + 1 & "); Size =(" & Width & " X " & Height & ")"
Next
Return Text
End Function

Reading ListView from BackgroundWorker

I've tried doing some searchs but for the life of me I don't seem to be able to find the answer, or a suggested solution that works. Its probably my understanding, but hopefully asking my own question will give me an answer that works :o)
I have a Windows Form Application which consists of one item, ListView1
This ListView has items added to it from a file via a Drag / Drop which is done on the main UI Thread, no background worker, it consists of around 1500 rows.
I'm trying to get a background worker now to read this ListView, but I'm getting a Cross Threading error as ListView1 was not created on the same thread.
The error comes on the simplest of pieces of code, but I don't seem to be able to think of a way around it or implementing an invoke etc.
For i = 0 To Me.ListView1.Items.Count - 1
ValueStatement = ValueStatement & "(" & Me.ListView1.Items(i).SubItems(0).Text
If i = Me.ListView1.Items.Count - 1 Or counter = 500 Then
CommaTerminate = ";"
Else
CommaTerminate = ","
End If
For y = 0 To Me.ListView1.Columns.Count - 1
ValueStatement = ValueStatement & "'" & Me.ListView1.Items(i).SubItems(y).Text & "'"
If y = Me.ListView1.Columns.Count - 1 Then
ValueStatement = ValueStatement & ")"
Else
ValueStatement = ValueStatement & ","
End If
Next
ValueStatement = ValueStatement & CommaTerminate & vbNewLine
If counter = 500 Then
SQLStatement = "INSERT INTO RAW_CLI_DATA_" & GlobalVariables.CDR_Company & " VALUES " & vbNewLine & ValueStatement
GenericDatabaseRequest(SQLStatement, "Loading RAW table with data..")
counter = 0
ValueStatement = ""
End If
counter = counter + 1
Next
The error comes on the line ValueStatement = ValueStatement & "(" & Me.ListView1.Items(i).SubItems(0).Text
Thanks for any help!
It sounds like you went down the wrong road early on. The ListView is supremely illsuited for database ops:
Everything is contained as String which means somewhere you will have code to convert it to other types
It does not support databinding which means you have to manually create rows...
... then later iterate them to fish the data back out.
A DataGridView and DataTable would be simpler: When the user enters data into the control and it would be stored in the table and as the proper type. Setting the DataTable as the Datasource, the DataGridView would create the display (rows and columns) for you.
Some of the time it takes will be consumed by SQLite to perform the INSERT, but it also looks like you are spending a lot of time iterating and concatenating SQL. It's usually better to work with the data than the user's View of it anyway, so extract and pass the data to the worker.
First, suck the data out of the ListView into a String()() container:
Dim data = lv.Items.
Cast(Of ListViewItem).
Select(Function(s) New String() {s.SubItems(0).Text,
s.SubItems(1).Text,
s.SubItems(2).Text}).
ToArray()
Then pass it to the BackGroundWorker:
bgw.RunWorkerAsync(data)
The DoWork Event:
Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
' unbox the data
Dim dataToInsert = CType(e.Argument, String()())
For n As Int32 = 0 To 2
Console.WriteLine("[{0}], [{1}], [{2}]", dataToInsert(n)(0),
dataToInsert(n)(1),
dataToInsert(n)(2))
Next
End Sub
Results:
[Patient Tempest], [Lorem ipsum dolor sit], [Swordfish]
[Sour Priestess], [hendrerit nibh tempor], [Perch]
[Frozen Justice], [Interdum ex felis], [Swordfish]
It correctly prints the random data I put into the LV.
This will allow you to process the ListView Data in the BackGroundWorker but it wont really save any time, it just keeps the UI unlocked. The real problem is elsewhere, probably in the DB ops.

FileInfo returning wrong value?

Okay, so I'm working in VB.NET, manually writing error logs to log files (yes, I know, I didn't make the call). Now, if the files are over an arbitrary size, when the function goes to write out the new error data, it should start a new file with a new file name.
Here's the function:
Dim listener As New Logging.FileLogTraceListener
listener.CustomLocation = System.Configuration.ConfigurationManager.AppSettings("LogDir")
Dim loc As String = DateTime.UtcNow.Year.ToString + DateTime.UtcNow.Month.ToString + DateTime.UtcNow.Day.ToString + DateTime.UtcNow.Hour.ToString + DateTime.UtcNow.Minute.ToString
listener.BaseFileName = loc
Dim logFolder As String
Dim source As String
logFolder = ConfigurationManager.AppSettings("LogDir")
If ex.Data.Item("Source") Is Nothing Then
source = ex.Source
Else
source = ex.Data.Item("Source").ToString
End If
Dim errorFileInfo As New FileInfo(listener.FullLogFileName)
Dim errorLengthInBytes As Long = errorFileInfo.Length
If (errorLengthInBytes > CType(System.Configuration.ConfigurationManager.AppSettings("maxFileSizeInBytes"), Long)) Then
listener.BaseFileName = listener.BaseFileName + "1"
End If
Dim msg As New System.Text.StringBuilder
If String.IsNullOrEmpty(logFolder) Then logFolder = ConfigurationManager.AppSettings("LogDir")
msg.Append(vbCrLf & "Exception" & vbCrLf)
msg.Append(vbTab & String.Concat("App: AppMonitor | Time: ", Date.Now.ToString) & vbCrLf)
msg.Append(vbTab & String.Concat("Source: ", source, " | Message: ", ex.Message) & vbCrLf)
msg.Append(vbTab & "Stack: " & ex.StackTrace & vbCrLf)
listener.Write(msg.ToString())
listener.Flush()
listener.Close()
I have this executing in a loop for testing purposes, so I can see what happens when it gets (say) 10000 errors in all at once. Again, I know there are better ways to handle this systemically, but this was the code I was told to implement.
How can I reliably get the size of the log file before writing to it, as I try to do above?
Well, as with many things, the answer to this turned out to be "did you read your own code closely" with a side order of "eat something, you need to fix your blood sugar."
On review, I saw that I was always checking BaseFileName and, if it was over the arbitrary limit, appending a character and writing to that file. What I didn't do was check to see if that file or, indeed, other more recent files existed. I've solved the issue be grabbing a directory list of all the files matching the "BaseFileName*" argument in Directory.GetFiles and selecting the most recently accessed one. That ensures that the logger will always select the more current file to write to or -if necessary- use as the base-name for another appended character.
Here's that code:
Dim directoryFiles() As String = Directory.GetFiles(listener.Location.ToString(), listener.BaseFileName + "*")
Dim targetFile As String = directoryFiles(0)
For j As Integer = 1 To directoryFiles.Count - 1 Step 1
Dim targetFileInfo As New FileInfo(targetFile)
Dim compareInfo As New FileInfo(directoryFiles(j))
If (targetFileInfo.LastAccessTimeUtc < compareInfo.LastAccessTimeUtc) Then
targetFile = directoryFiles(j)
End If
Next
Dim errorFileInfo As New FileInfo(listener.Location.ToString() + targetFile)
Dim errorLengthInBytes As Long = errorFileInfo.Length

Logic help for DotNetZip loop logic

I have a logic problem, just need more brains working on this.
For Each JobNode In JobNodes
Dim Source = JobNode.SelectNodes("Source")
For Each item As System.Xml.XmlNode In Source
Dim infoReader As System.IO.FileInfo
''''Discerns whether a file or directory
If item.InnerText.Contains(".") Then 'is a file
If Dir$(item.InnerText) <> vbNullString Then
mySize += FileLen(item.InnerText)
End If
zip.AddFile(item.InnerText, "")
Dim numFiles2 = filenames.Count
mySize = BytesTO(mySize, convTo.MB)
Console.WriteLine("...Added all " & numFiles2 & " files. Total loose file collection size is " & Math.Round(mySize, 2) & " MB")
Console.WriteLine(vbCrLf)
Else 'is a directory
zip.AddDirectory(item.InnerText, GetLastDirName(item.InnerText & " "))
Dim dinfo As New DirectoryInfo(item.InnerText)
Dim sizeOfDir As Long = DirectorySize(dinfo, True)
Dim numFiles As Integer = CountFiles_FolderAndSubFolders(item.InnerText)
Console.WriteLine("...Added all " & numFiles & " files. Total directory size is {0:N2} MB", (CDbl(sizeOfDir)) / (1024 * 1024))
Console.WriteLine(vbCrLf)
End If
Next
This is part of a backup program I made. This is the part that determines if the object to be added to the zip is a file (first part of the IF statement) or a directory (ELSE part of the IF statement).
My problem is, I am trying to add this code:
Dim numFiles2 = filenames.Count
mySize = BytesTO(mySize, convTo.MB)
Console.WriteLine("...Added all " & numFiles2 & " files. Total loose file collection size is " & Math.Round(mySize, 2) & " MB")
Console.WriteLine(vbCrLf)
Just after the zip.AddFile(item.innertext,"") part. I want to add this so that I can get a console print out of the information.
The problem is, if I just put it directly after the zip.addfile(), it outputs the code directly above each time. I only want it to print out that part (directly above) once it has finished adding all of the loose files (or files added using zip.AddFile())
EDIT: adding the for loops at the top for more clarification
Add a flag and then look for it after the loop completes. It seems as though you are looping through the files instead of calling this recursively. I am guessing you have only one loop right now.
Before Loop iteration:
Dim FilesBeingAdded as Boolean = False
Dim numFiles2 as Integer = 0
Inside of iteration:
If item.InnerText.Contains(".") Then 'is a file
If FilesBeingAdded = False Then
FilesBeingAdded = true
End If
If Dir$(item.InnerText) <> vbNullString Then
mySize += FileLen(item.InnerText)
End If
zip.AddFile(item.InnerText, "")
numFiles2 +=1
Else 'is a directory
zip.AddDirectory(item.InnerText, GetLastDirName(item.InnerText & " "))
Dim dinfo As New DirectoryInfo(item.InnerText)
Dim sizeOfDir As Long = DirectorySize(dinfo, True)
Dim numFiles As Integer = CountFiles_FolderAndSubFolders(item.InnerText)
Console.WriteLine("...Added all " & numFiles & " files. Total directory size is {0:N2} MB", (CDbl(sizeOfDir)) / (1024 * 1024))
Console.WriteLine(vbCrLf)
End If
After Loop Iteration:
If FilesBeingAdded = True Then
mySize = BytesTO(mySize, convTo.MB)
Console.WriteLine("...Added all " & numFiles2 & " files. Total loose file collection size is " & Math.Round(mySize, 2) & " MB")
Console.WriteLine(vbCrLf)
mySize = 0
numFiles2 = 0
FilesBeingAdded = False
End If
So when it finishes the current iteration it will take the tally of files it added, along with the size and print it to the console. It then zeros the variables so that the next iteration of the inner loop has clean values. I am presuming you run this on more than one directory. By checking FilesBeingAdded you can be sure to avoid situations where an ugly zero count output is shown if the directory contained no files.
This solution presumes a lot about how you enter this piece of code. If my presumptions were not correct, please modify the question to enable a better solution.