how to prevent the Image.FromFile() method to lock the file - vb.net

I am using following code to put JPG's into a DataGridView's Image cell.
If strFileName.ToLower.EndsWith(".jpg") Then
Dim inImg As Image = Image.FromFile(strFileName)
DataGridView4.Rows.Add()
DataGridView4.Rows(DataGridView4.Rows().Count - 1).Cells(0).Value = inImg
End If
The problem is that I need to save this file from within the program, but i get the message that the file is beeing used by another program.
So i tried to add inImg.Dispose() before the end if, but then the program doesnt display the images anymore in the DataGridView.
How can i add images in the DataGridView without locking them?
thanks

When you use the Image.FromFile(strFileName) method to create the Image, the method locks the file until you release the Image. The exact reason is explained below. And it's why you can't access more than one time to the same image file with this method.
You could instead:
use the Image.FromStream(stream) method.
that you use with a New FileStream or a MemoryStream that you create from the image file.
Here are possible implementation of a custom SafeImageFromFile method that doesn't lock the image file:
Public Shared Function SafeImageFromFile(path As String) As Image
Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
Dim img = Image.FromStream(fs)
Return img
End using
End Function
Or
Public Shared Function SafeImageFromFile(path As String) As Image
Dim bytes = File.ReadAllBytes(path)
Using ms As New MemoryStream(bytes)
Dim img = Image.FromStream(ms)
Return img
End Using
End Function
Usage
If strFileName.ToLower.EndsWith(".jpg") Then
Dim inImg As Image = SafeImageFromFile(strFileName)
Dim index as integer = DataGridView4.Rows.Add()
DataGridView4.Rows(index).Cells(0).Value = inImg
End If
Important note
Here I create the FileStream or a MemoryStream using a Using statement to make sure the stream is released. It works fine on my system and it seems it work for you too, though MSDN says about Image.FromStream(stream) method:
You must keep the stream open for the lifetime of the Image.
The reason of this sentence is explain here: KB814675 Bitmap and Image constructor dependencies
GDI+, and therefore the System.Drawing namespace, may defer the
decoding of raw image bits until the bits are required by the image.
Additionally, even after the image has been decoded, GDI+ may
determine that it is more efficient to discard the memory for a large
Bitmap and to re-decode later. Therefore, GDI+ must have access to the
source bits for the image for the life of the Bitmap or the Image
object.
To retain access to the source bits, GDI+ locks any source file, and
forces the application to maintain the life of any source stream, for
the life of the Bitmap or the Image object.
So know the code above could generate GDIexceptions because of releasing the stream using Using. It could happen when you save the image from the file or during the image creation. From this thread Loading an image from a stream without keeping the stream open and Hans Passant's comment they fixed several problems with indexed pixel formats in the Vista version of gdiplus.dll., it would happen only on XP.
To avoid this you need to keep the stream open. The methods would be:
Public Shared Function SafeImageFromFile(path As String) As Image
Dim fs As New FileStream(path, FileMode.Open, FileAccess.Read)
Dim img = Image.FromStream(fs)
Return img
End Function
Or
Public Shared Function SafeImageFromFile(path As String) As Image
Dim bytes = File.ReadAllBytes(path)
Dim ms = New MemoryStream(bytes)
Dim img = Image.FromStream(ms)
Return img
End Function
But those last methods have some disadvantage like not releasing the stream (memory issue) and they violate rule CA2000 Dispose objects before losing scope .
The KB article gives some workarounds:
Create a Non-Indexed Image
This approach requires that the new image be in a non-indexed pixel
format (more than 8 bits-per-pixel), even if the original image was in
an indexed format. This workaround uses the Graphics.DrawImage()
method to copy the image to a new Bitmap object:
Construct the original Bitmap from the stream, from the memory, or from the file.
Create a new Bitmap of the same size, with a pixel format of more than 8 bits-per-pixel (BPP).
Use the Graphics.FromImage() method to obtain a Graphics object for the second Bitmap.
Use Graphics.DrawImage() to draw the first Bitmap onto the second Bitmap.
Use Graphics.Dispose() to dispose of the Graphics.
Use Bitmap.Dispose() to dispose of the first Bitmap.
Create an Indexed Image
This workaround creates a Bitmap object in an indexed format:
Construct the original Bitmap from the stream, from the memory, or from the file.
Create a new Bitmap with the same size and pixel format as the first Bitmap.
Use the Bitmap.LockBits() method to lock the whole image for both Bitmap objects in their native pixel format.
Use either the Marshal.Copy function or another memory copying function to copy the image bits from the first Bitmap to the second Bitmap.
Use the Bitmap.UnlockBits() method to unlock both Bitmap objects.
Use Bitmap.Dispose() to dispose of the first Bitmap.
Here is an implementation of Non-Indexed Image creation, based on KB article and this answer https://stackoverflow.com/a/7972963/2387010 Your best bet is creating a pixel-perfect replica of the image -- though YMMV (with certain types of images there may be more than one frame, or you may have to copy palette data as well.) But for most images, this works:
Private Shared Function SafeImageFromFile(path As String) As Bitmap
Dim img As Bitmap = Nothing
Using fs As New FileStream(path, FileMode.Open, FileAccess.Read)
Using b As New Bitmap(fs)
img = New Bitmap(b.Width, b.Height, b.PixelFormat)
Using g As Graphics = Graphics.FromImage(img)
g.DrawImage(b, Point.Empty)
g.Flush()
End Using
End Using
End Using
Return img
End Function
Someone indicated that what is important is that the FileStream is opened in read mode (FileAccess.Read).
True, but it makes more sens if you don't use Using statement and so you don't release the stream, or in multi threads context: FileAccess.Write is inappropriate, and FileAccess.ReadWrite is not required, but open the stream with FileAccess.Read mode won't prevent to have an IO.Exception if another program (or yours in multi threads context) has opened the file with another mode than FileAccess.Read.
If you want to be able to display the image and at the same time be able to save data to the file, Since you don't lock the file with those methods, you should be able to save the image (delete/overwrite the previous file) using the Image.Save method.

# Chris: Opening approximately 100 large (3400x2200) images with your final code, I was receiving an invalid argument crash on [img = new bitmap(...], I have seen this before opening an image of zero size, but that was not the case here. I added fs.dispose and successfully opened thousands of images of the same size of the same set as the first test without issue. I'm interested in your comments on this.
Private Function SafeImageFromFile(FilePath As String) As Image
Dim img As Bitmap = Nothing
Using fs As New FileStream(FilePath, FileMode.Open, FileAccess.Read)
Using b As New Bitmap(fs)
img = New Bitmap(b.Width, b.Height, b.PixelFormat)
Using g As Graphics = Graphics.FromImage(img)
g.DrawImage(b, Point.Empty)
g.Flush()
End Using
End Using
fs.Dispose()
End Using
Return img
End Function

This works without issue, ran 4189 images 3400x2200 through it (twice) without issue, this moves the filestream outside of the function and re-uses it. Im closing the file to release the write lock. Im pointing a picturebox at this image in a loop for my test.
Private fsIMG As FileStream
Private Function SafeImageFromFile(FilePath As String) As Image
'Ref: http://stackoverflow.com/questions/18250848/how-to-prevent-the-image-fromfile-method-to-lock-the-file
Dim img As Bitmap = Nothing
fsIMG = New FileStream(FilePath, FileMode.Open, FileAccess.Read)
Using b As New Bitmap(fsIMG)
img = New Bitmap(b.Width, b.Height, b.PixelFormat)
Using g As Graphics = Graphics.FromImage(img)
g.DrawImage(b, Point.Empty)
g.Flush()
End Using
End Using
fsIMG.Close()
Return img
End Function

After searching the internet for long time I found out I can use this code without any error.
Private fsIMG As FileStream
Private Function SafeImageFromFile(FilePath As String) As Image
'Ref: http://stackoverflow.com/questions/18250848/how-to-prevent-the-image-fromfile-method-to-lock-the-file
Dim img As Bitmap = Nothing
fsIMG = New FileStream(FilePath, FileMode.Open, FileAccess.Read)
Using b As New Bitmap(fsIMG)
img = New Bitmap(b.Width, b.Height, b.PixelFormat)
Using g As Graphics = Graphics.FromImage(img)
g.DrawImage(b, Point.Empty)
g.Flush()
End Using
End Using
fsIMG.Close()
Return img
End Function

I encountered the same situation and used this code:
' Create memory stream from file
Dim ms As New MemoryStream()
' Open image file
Using fs As New FileStream(.FileName, FileMode.Open, FileAccess.Read)
' Save to memory stream
fs.CopyTo(ms)
End Using
' Create image from the file's copy in memory
Dim img = Image.FromStream(ms)
I didn't dispose the memory stream because it allows to save the image later using exactly the same encoding as the original file, using this code:
img.Save(someOtherStream, img.RawFormat)

Related

VB.NET Display Image in PictureBox from Binary DB Value

I am trying to take a database binary entry where images are stored and convert it back to display in a PictureBox on a Windows Form. I have looked around and tried to piece a couple methods together however i get no errors and also no image in the PictureBox when trying to use the below code.
Dim MyByte = varReader("BinaryData")
Dim MyImg As Image
If MyByte IsNot Nothing Then
MyImg = bytesToImage(MyByte)
PictureBox1.Image = MyImg
End If
Public Function bytesToImage(ByVal byteArrayIn As Byte()) As Image
Dim ms As MemoryStream = New MemoryStream(byteArrayIn)
Dim returnImage As Image = Image.FromStream(ms)
Return returnImage
End Function
I know the Binary data is good as i have a different piece of code that has created it in the first place to store in the DB and the image displays fine on the Website which uses this data.
Any help or advice where i have gone wrong would be much appreciated!!
Many Thanks
EDITED::
The database column holding the information is varbinary(MAX)
This is the code i use to convert the image to the binary (I won't include the DB update part as this is just a basic update to the DB Column)
Dim varPictureBinary As Byte()
Dim filePath As String = "ImageFilePath"
Dim fStream As FileStream = New FileStream(filePath, FileMode.Open, FileAccess.Read)
Dim br As BinaryReader = New BinaryReader(fStream)
Dim fileInfo As FileInfo = New FileInfo(filePath)
varPictureBinary = br.ReadBytes(fileInfo.Length)
I then want to take a binary entry from this column and turn it back to an image and display it in a picture box in my program.

VB.NET 2010 - Extract/Save ICO file from My.Resources

Title says it all, How do I save a file from the application resources to the desktop for example?
In the future it will create a directory and store it in there, essentially I'm making a game installer, but none of this matters right now.
I have tried about 6 different code methods and all of them failed, kept throwing errors about not being able to convert an icon/image to a 1-dimensional array. Or that "Icon" or "Image" is not a member of System.Drawing.Icon
Any help would be appreciated
Thank you!
EDIT: Posting some code I've tried.
#1 ('Length' is not a member of System.Drawing.Icon)
Dim File01 As System.IO.FileStream = New System.IO.FileStream("C:\Users\" + Environment.UserName + "\Desktop\" + "EXAMPLE_Icon.ico", IO.FileMode.Create) File01.Write(My.Resources.EXAMPLE_Icon, 0, My.Resources.EXAMPLE_Icon.length) File01.Close()
#2 ('System.Drawing.Icon' cannot be converted to '1-dimensional array of Byte)
Dim filePath = Path.Combine("C:\Users\" + Environment.UserName + "\Desktop\", "EXAMPLE_Icon.ico") File.WriteAllBytes(filePath, My.Resources.EXAMPLE_Icon)
#3 ('RawFormat' is not a member of 'System.Drawing.Icon')
Dim filePath = Path.Combine("C:\Users\" + Environment.UserName + "\Desktop\", "EXAMPLE_Icon.ico") Using icon = My.Resources.EXAMPLE_Icon icon.Save("C:\Users\" + Environment.UserName + "\Desktop\", icon.RawFormat) End Using
Build the destination path using Path.Combine() when the path is composited.
Many known System locations have a predefined value in Environment.SpecialFolder: in your case, Environment.SpecialFolder.Desktop. Environment.GetFolderPath() accepts one of these values and returns the correct file system path.
This returns the Path of the Desktop folder for the current User:
Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
The Icon object has a Save() method that accepts a Stream as argument.
You can pass a FileStream to save the Icon to a File, preserving its format, or a MemoryStream, then use File.WriteAllBytes() to save the MemoryStream's buffer calling its ToArray() method.
Using a Resource name, as a string, using ResourceManager.GetObject():
Dim iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "myIcon.ico")
Using stream As New FileStream(iconPath, FileMode.Create, FileAccess.Write, FileShare.None),
ico = TryCast(My.Resources.ResourceManager.GetObject("myIcon"), Icon)
ico?.Save(stream)
End Using
Or, depending on what fits better in the context of your operations:
Using stream As New FileStream(iconPath, FileMode.Create, FileAccess.Write, FileShare.None)
My.Resources.myIcon.Save(stream)
End Using
GC.Collect()
Note: here, I'm calling GC.Collect() after calling Save() directly on the Icon object generated by My.Resources. The Docs don't exactly explain it: My.Resources is a factory, it doesn't return the object stored in the Project's Resources, it generates a new object (a different one each time) from the data stored as a Resource. With My.Resources.myIcon.Save(stream), we create a new object, save its data to disc, but never actually dispose of it. Calling GC.Collect() right after, the memory used is reclaimed immediately. If we don't, that memory is never reclaimed (we're leaking).
It's probably better to assign the generated object a to temporary variable declared with a Using statement. The two methods that follow can make use of GC.Collect(), calling it right after, to reclaim the allocated memory immediately, but it's not strictly required, we're not really leaking resources.
Open up Visual Studio's Diagnostic Tools to test the behavior with and without GC.Collect(), calling these methods multiple times.
Using stream As New FileStream(iconPath, FileMode.Create, FileAccess.Write, FileShare.None),
ico = My.Resources.myIcon
ico.Save(stream)
End Using
' GC.Collect() <= Not strictly required, but test the difference
Or using a MemoryStream:
Using ms As New MemoryStream(),
ico As Icon = My.Resources.myIcon
ico.Save(ms)
ms.Position = 0
File.WriteAllBytes(iconPath, ms.ToArray())
End Using
' GC.Collect() <= Not strictly required, but test the difference

Resizing multiple images and saving them to a separate folder

I need to resize and compress 200 images that I have stored in a folder.
I am getting these images in a list using this code that I got from another question:
Dim dir = New IO.DirectoryInfo("C:\\Users\\Charbel\\Desktop\\Images")
Dim images = dir.GetFiles("*.jpg", IO.SearchOption.AllDirectories).ToList
Dim pictures As New List(Of PictureBox)
For Each img In images
Dim picture As New PictureBox
picture.Image = Image.FromFile(img.FullName)
pictures.Add(picture)
Next
Now, I need to compress and reduce each image to (500x374) and then save them in another folder on my PC.
Well, let me first point out a couple of points about your code:
PictureBox doesn't serve any purpose here. You shouldn't create a PictureBox to use the Image.
Always remember to dispose the Image object (e.g., by wrapping it in a Using block) so you don't run into memory issues.
Unlike C#, VB.NET doesn't require escaping the \ character, therefore, you can write your path like this "C:\Users...".
Now, for resizing the image, you can simply create an instance of the Bitmap class with the constructor that takes an image and a size argument: Bitmap(Image, Size) or Bitmap(Image, Int32, Int32).
Here:
Dim sourcePath As String = "C:\Users\Charbel\Desktop\Images"
Dim outputPath As String = "C:\Users\Charbel\Desktop\Images\Resized"
IO.Directory.CreateDirectory(outputPath)
Dim dir = New IO.DirectoryInfo(sourcePath)
Dim files As IO.FileInfo() = dir.GetFiles("*.jpg", IO.SearchOption.AllDirectories)
For Each fInfo In files
Using img As Bitmap = Image.FromFile(fInfo.FullName)
Using resizedImg As New Bitmap(img, 500, 374)
resizedImg.Save(IO.Path.Combine(outputPath, fInfo.Name),
Imaging.ImageFormat.Jpeg)
End Using
End Using
Next

bitmap image printing using axiohm usbcomm dll

I am using an Axiohm thermal printer for printing POS receipt(USBCOMM.dll for communication). Currently, i am able to print the required details along with an image(.bmp file). Now i need to use a new image instead of the existing image. The new image contains barcode.
When i try printing the new image, all i get is some garbage values. Below is the code that i use. Same code works with old image but not with the new image. Is there any format for image that i need to follow.
Dim filepath As String = AppDomain.CurrentDomain.BaseDirectory + "Resources\PrinterDlls\unnamed.bmp"
Using fs = New FileStream(filepath, FileMode.Open, FileAccess.Read, FileShare.Read)
Dim inpt As Byte() = New Byte(fs.Length) {}
inpt(0) = &H1F
fs.Read(inpt, 1, CInt(fs.Length))
Dim ok As Boolean = Usb_WritePort(True, inpt, inpt.Length, written, IntPtr.Zero)
If Not ok OrElse written <> inpt.Length Then
Throw New Exception("USB write failed")
End If
End Using
Well, this is embarrassing that i am answering my own question. I searched for sometime to resolve and raised the question. Soon after, i came across this video in youtube that explain the bitmap image to create for thermal printing
https://www.youtube.com/watch?v=LdB33eWLjgU
Basically, you need to ensure 3 things while creating the image:
1. 8-bit
2. Greyscale
3. Save as .bmp
And the new image will work like a charm while printing. Also ensure the width is less than the paper width.

Is it possible to view multi page .Tif files in vb.net application?

I am hoping to be able to view .Tif files in my vb.net application - is there a component that can be used to do that. I've been messing around with Tiff Viewer but for some reason the file is stretched beyond anything - you can barely make out what it says. I tried to adjust the width of the viewer but it did not do much, it is still stretched horizontally.
Based on https://stackoverflow.com/a/401579/741136, this will load a multiframe tif into a collection of single frames:
Function LoadTif(filename As String) As List(Of Image)
Dim lstTif As New List(Of Image)
Dim bmp As Bitmap = DirectCast(Image.FromFile(filename), Bitmap)
For i As Integer = 0 To bmp.GetFrameCount(Imaging.FrameDimension.Page) - 1
bmp.SelectActiveFrame(Imaging.FrameDimension.Page, i)
Dim ms As New System.IO.MemoryStream
bmp.Save(ms, Imaging.ImageFormat.Tiff)
lstTif.Add(Image.FromStream(ms))
ms.Dispose()
Next i
Return lstTif
End Function