Merge large monochromatic (BW) images - vb.net

I need to work with large monochromatic images. I basically need to add a banner (frame and formatted text) to a hi-res (like 30000x20000px) monochromatic images (A2+ plots at 400dpi).
The default PixelFormat is Format1bppIndexed, which makes even those large picture relatively small in size. However, using .NET GDI+ Graphics object requires unindexed bitmap.
When turning the image to lowest available unindexed PixelFormat.Format16bppGrayscale:
bmpResized = ImgResized.Clone(New System.Drawing.Rectangle(0, 0, ImgResized.Width, ImgResized.Height),
System.Drawing.Imaging.PixelFormat.Format16bppGrayScale)
...it becomes to large to be handled by Image and Bitmap (OutOfMemoryExeption: Not enough memory - on 32GB machine).
I tried to create the banner separately and join the pictures pixel-wise, however I ran into the very same limitations - SetPixel requires unindexed bitmap.
Is there any way to overcome those issues?
EDIT: A possible solution would be to create a byte array and edit the bytes as per [https://social.msdn.microsoft.com/Forums/vstudio/en-US/54a096ff-46f3-45ce-8560-bf5a0618ef75/how-to-set-pixel-into-bitmap-with-pixel-format-of-format8bppindexed-?forum=csharpgeneral][1]. However I'm not sure how exactly I can "shift" the 2nd image byte-wise.

Finally I had to do it using bit-wise manipulations. It worked out perfectly, even the performance is very good.
' Create byte array for the main ploted TIF image
Dim bmp As New Drawing.Bitmap(Img.Width, Img.Height, Drawing.Imaging.PixelFormat.Format1bppIndexed)
bmp = Img ' getting bitmap from the default image
Dim data As System.Drawing.Imaging.BitmapData = bmp.LockBits(New Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), Drawing.Imaging.ImageLockMode.[WriteOnly], Drawing.Imaging.PixelFormat.Format1bppIndexed)
Dim bytes As Byte() = New Byte(data.Height * data.Stride - 1) {}
System.Runtime.InteropServices.Marshal.Copy(data.Scan0, bytes, 0, bytes.Length)
' Create byte array for the banner
Dim bmpB As New Drawing.Bitmap(Img.Width, Img.Height, Drawing.Imaging.PixelFormat.Format1bppIndexed)
bmpB = Img.Clone() ' banner had to be generated in 16bppp unindexed gray scale image to be able to use graphics
Dim dataB As System.Drawing.Imaging.BitmapData = bmpB.LockBits(New Drawing.Rectangle(0, 0, BmpB.Width, BmpB.Height), Drawing.Imaging.ImageLockMode.[WriteOnly], Drawing.Imaging.PixelFormat.Format1bppIndexed)
Dim bytesB As Byte() = New Byte(dataB.Height * dataB.Stride - 1) {}
System.Runtime.InteropServices.Marshal.Copy(dataB.Scan0, bytesB, 0, bytesB.Length)
' Join byte arrays: Resulting bitmap = plotted TIF + banner
Dim bytesResult((bytes.Length + bytesB.Length) - 1) As Byte
bytesB.CopyTo(bytesResult, 0)
bytes.CopyTo(bytesResult, bytesB.Length)
' Revert resulting byte array back to bitmap and save resulting TIF image
BmpResized = New Bitmap(Img.Width, Img.Height * 2, Drawing.Imaging.PixelFormat.Format1bppIndexed)
Dim dataResult As System.Drawing.Imaging.BitmapData = BmpResized.LockBits(New Drawing.Rectangle(0, 0, BmpResized.Width, BmpResized.Height), Drawing.Imaging.ImageLockMode.[WriteOnly], Drawing.Imaging.PixelFormat.Format1bppIndexed)
System.Runtime.InteropServices.Marshal.Copy(bytesResult, 0, dataResult.Scan0, bytes.Length + bytesB.Length - 0)
Dim exportFileName As String = filenames1(0).Replace(".tif", "_mod.tif")
exportFileName = exportFileName.Replace(".png", "_mod.tif")
BmpResized.Save(exportFileName, Drawing.Imaging.ImageFormat.Tiff)
' Unlock locked bitmaps
bmp.UnlockBits(dataResult)
BmpResized.UnlockBits(dataResult)
' dispose anything not used later...

Related

Notification When Screen is Flashing VB.Net

I would like a notification to be triggered when part the screen starts to flash. This notification can be a msgbox for now, but I will eventually evolve it into an audible sound.
The purpose of this is we have a dashboard that displays various cells throughout the company. When a cell needs assistance, its spot on the dashboard starts to flash. The cells are displayed in horizontally stackedboxes like this;
Cell 1
Cell 2
Cell 3
Ect...
I would like to build an application that scans the screen, lets say every second, and gets each cells pixel intensity.
The notification will be triggered if/when the cells pixel intensity changes each scan for three consecutive scans in a row (ie. the cell must be flashing).
I am hoping that you guys can help me find a way to scan the screen an return a regions average pixel intensity to which I can then replicate and do the comparison to find out if it is flashing.
Thank you in advance, I am using VB.Net.
I was able to accomplish what I was asking by using this:
Private Sub AvgColors(ByVal InBitmap As Bitmap)
Dim btPixels(InBitmap.Height * InBitmap.Width * 3 - 1) As Byte
Dim hPixels As GCHandle = GCHandle.Alloc(btPixels, GCHandleType.Pinned)
Dim bmp24Bpp As New Bitmap(InBitmap.Width, InBitmap.Height, InBitmap.Width * 3,
Imaging.PixelFormat.Format24bppRgb, hPixels.AddrOfPinnedObject)
Using gr As Graphics = Graphics.FromImage(bmp24Bpp)
gr.DrawImageUnscaledAndClipped(InBitmap, New Rectangle(0, 0,
bmp24Bpp.Width, bmp24Bpp.Height))
End Using
Dim sumRed As Int32
Dim sumGreen As Int32
Dim sumBlue As Int32
For i = 0 To btPixels.Length - 1 Step 3
sumRed += btPixels(i)
sumGreen += btPixels(i + 1)
sumBlue += btPixels(i + 2)
Next
hPixels.Free()
Dim avgRed As Byte = CByte(sumRed / (btPixels.Length / 3))
Dim avgGreen As Byte = CByte(sumGreen / (btPixels.Length / 3))
Dim avgBlue As Byte = CByte(sumBlue / (btPixels.Length / 3))
MsgBox(avgRed & vbCrLf & avgGreen & vbCrLf & avgBlue)
End Sub
Private Function Screenshot() As Bitmap
Dim b As Bitmap = New Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
Using g As Graphics = Graphics.FromImage(b)
g.CopyFromScreen(0, 0, 0, 0, b.Size, CopyPixelOperation.SourceCopy)
g.Save()
End Using
Return b
End Function
and from here I can just adjust the range of bitmap to what I need, add a timer to tick every second, and keep a variable to compare average RGB's to.
Most of the code found from here:
http://www.vbforums.com/showthread.php?776021-RESOLVED-Getting-Average-RGB-Color-Value-of-Entire-Screen

Put images side by side

How do I combine two images in VB.net to create one big image. They are both 1920x1080 jpegs and I'd like it to come out in a 3840x1080 jpeg.
Like this: I get these images in:
image 1
And get this out: image 1 side by side with image 2
I found it. The code is pretty easy, basically two images in one image out.
Dim ImageOne As System.Drawing.Image = Image.FromFile("img1")
Dim ImageTwo As System.Drawing.Image = System.Drawing.Image.FromFile("img2")
'replace path of image two with Image2.ImageUrl
Dim NewImageHeight As Integer = If(ImageOne.Height > ImageTwo.Height, ImageOne.Height, ImageTwo.Height)
'To calculate height of new image
Dim NewImageWidth As Integer = ImageOne.Width + ImageTwo.Width
' width of new image
Dim NewImageBmp As New Bitmap(NewImageWidth, NewImageHeight, Imaging.PixelFormat.Format32bppArgb)
' you can change the bpp as per your requirment. Size of image directly propotionate to bpp of image
Dim NewImageGrx As Graphics = System.Drawing.Graphics.FromImage(NewImageBmp)
NewImageGrx.DrawImageUnscaled(ImageOne, 0, 0)
'draw first image at coordinate 0,0
NewImageGrx.DrawImageUnscaled(ImageTwo, ImageOne.Width, 0)
'draw second image at coordinate image1.width,0
Dim CombineImage As String = Guid.NewGuid().ToString() + ".jpg"
NewImageBmp.Save("output file", ImageFormat.Jpeg)
' saving combined image. You can specify the ImageFormat as per your requirment.
'disposing objects after use
ImageOne.Dispose()
ImageTwo.Dispose()
NewImageBmp.Dispose()
NewImageGrx.Dispose()

Photoshop crop from vb.net

I assumed that cropping an image would be an extremely easy thing to do from .net. But no matter what I try I just cannot seem to get the thing to work.
The documentation is somewhat vague -
'The first parameter is an array of four coordinates that mark the portion remaining after cropping'
That could mean an array of four numbers, or it could mean an array of four arrays of two numbers (a coordinate after all consists of two numbers). the 'portion remaining after cropping' I take to mean 'the portion of the image designated to remain after cropping'.
Since the select function takes an array of coordinate arrays -- {{x1,y1}, y2, y2}, {x3,y3}, {x4, y4}} -- I had hoped crop would work the same way. No dice.
Next, I tried the really simple approach, assume that 'left, top, right, bottom' really mean just that. So, I plugged in perfectly reasonable values and ... no dice.
In every case, PS throws a dialog box saying ' Could not complete the command because the affected area is empty or does not overlap the canvas'.
Here is a code snippet:
Dim PSDapp
PSDapp = CreateObject("Photoshop.Application")
Dim psarray As Object = {20, 20, 120, 120}
Dim PSDcurrentDoc
PSDapp.preferences.rulerUnits = 1
PSDcurrentDoc = PSDapp.open("c:\cat.bmp")
PSDapp.activeDocument = PSDapp.documents.item(1)
PSDcurrentDoc.crop(psarray)
What is even more strange is that if I take the above code and port it to a script, it runs just fine. Can someone (anyone!) please please post a minimal working example of using the crop feature using COM (not scripting)?
I've never used Photoshop, but an array of coordinates could be written like this:
Dim psarray() As Point = {
New Point(20, 20),
New Point(120, 20),
New Point(120, 120),
New Point(20, 120)
}
PSDcurrentDoc.crop(psarray)
So you tried something like this already?
Dim psarray() As Integer = {20, 20, 120, 120}
PSDcurrentDoc.crop(psarray)
If that doesn't work, try "pinning" it:
Dim psarray() As Integer = {20, 20, 120, 120}
Dim gch As System.Runtime.InteropServices.GCHandle
gch = System.Runtime.InteropServices.GCHandle.Alloc(psarray, Runtime.InteropServices.GCHandleType.Pinned)
PSDcurrentDoc.crop(gch.AddrOfPinnedObject)
gch.Free()
Solved, with a work-around.
I used a selection as a work-around. The code is longer than it really should have to be since I must first make a selection, but it does work.
Below is the full working subroutine connected to a button. I hope it is of use to somebody that might face this issue as well.
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim x As Integer = 100 ' The x-coordinate of the upper-left corner (pixel units).
Dim y As Integer = 100 ' The y-coordinate of the upper-left corner (pixel units).
Dim w As Integer = 200 ' The width of the selection/crop (pixel units).
Dim h As Integer = 200 ' The height of the selection/crop (pixel units).
Dim psArray1() As Object = {x, y}
Dim psArray2() As Object = {x, y + h}
Dim psArray3() As Object = {x + w, y + h}
Dim psArray4() As Object = {x + w, y}
Dim psArray() As Object = {psArray1, psArray2, psArray3, psArray4} ' A concatenated object consisting of an array of coordinates.
Dim PSDapp As Object = CreateObject("Photoshop.Application")
Dim PSDcurrentDoc As Object = PSDapp.open("c:\cat.bmp") ' Could be any document of course.
PSDapp.preferences.rulerUnits = 1
PSDcurrentDoc.selection.select(psArray)
Dim selectArray As Object = PSDcurrentDoc.selection.bounds
PSDcurrentDoc = PSDcurrentDoc.crop(selectArray) ' This is key. PSDcurrentDoc.crop is read-only, so it must be assigned.
End Sub

How to resize image according to original image ration?

The white background after re-sizing the image using VB.net as shown below. I tried to change some values but failed.
Dim FileToResize As String = Server.MapPath("~/images/" & filename & FileUpload1.FileName)
Using originalBitmap As Bitmap = Bitmap.FromFile(FileToResize, True), newbmp As Bitmap = New Bitmap(200, 200)
Dim WidthVsHeightRatio = CDec(originalBitmap.Width) / CDec(originalBitmap.Height)
Using newg As Graphics = Graphics.FromImage(newbmp)
newg.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
newg.Clear(Color.White)
If WidthVsHeightRatio = 1D Then
newg.DrawImage(originalBitmap, 0, 0, 200, 200)
newg.Save()
ElseIf WidthVsHeightRatio < 1D Then 'Image is taller than wider
newg.DrawImage(originalBitmap, New RectangleF(New PointF((100 - (200 * WidthVsHeightRatio) / 2), 0), New SizeF(200 * WidthVsHeightRatio, 200.0F)))
newg.Save()
Else 'Image is wider than taller
Dim inverse As Double = Math.Pow(WidthVsHeightRatio, -1)
newg.DrawImage(originalBitmap, New RectangleF(New PointF(0, 100 - ((200 * inverse) / 2)), New SizeF(200.0F, 200 * inverse)))
newg.Save()
End If
End Using
newbmp.Save(Server.MapPath("~/images/" & "_th_" & filename & FileUpload1.FileName), System.Drawing.Imaging.ImageFormat.Jpeg)
NewsItem.ImageLink = filename & FileUpload1.FileName
NewsItem.smallimage = "_th_" & filename & FileUpload1.FileName
End Using
I did a bit of research recently for generating Image Thumbnails on the fly for an eCommerce site. I started off doing this myself generating a bitmap and then resizing etc. After problems with image size on disc and quality I looked in to http://imageresizing.net/ and I haven't looked back since. It can generate images from byte(), streams and physicals files all very quickly with one line of code:
ImageBuilder.Current.Build(New MemoryStream(bImage), sImageLocation + sFullFileName, New ResizeSettings("maxwidth=214&maxheight=238"))
I would definitely recommend this component rather than trying to reinvent the wheel...

Improving performance when working on Images with ASP.NET in VB

I am trying to speed up various aspects of my website.
The site is a shopping site various search pages which are slow. There are several potential reasons for this, one being working with images.
The other night I created a function which generates a thumbnail for my Facebook OG tags. The function writes a square image to the server 400px x 400px created by tiling the first few (up to 14) product images generated by a search query.
Running this script in total isolation on a test.aspx file gives me reasonable load time, but nothing I can do can speed it up, even when I don't return anything in the browser, just doing this process is taking about 3 seconds, which obviously expanded across the whole live site slows things down.
The entire code is:
Function mergeImgs(idList, imgList, head, fileName) As String
Try
Dim maxRows = HttpContext.Current.Request.QueryString("r")
Dim imgDim = 400
Dim Image3 As New Bitmap(imgDim, imgDim)
Dim g As Graphics = Graphics.FromImage(Image3)
Dim i = 0
Dim left = 0
Dim rows = 0
For Each item In idList
Dim img = Common.getImageUrl(idList(i), imgList(i), "server", "-tb")
i += 1
If img <> "/Images/awaiting.png" Then
Dim imageData As System.Drawing.Image = System.Drawing.Image.FromFile(img)
Dim aspect As Double = imageData.Width / imageData.Height ' determine aspect and decide how to display image
Dim Image1
Dim fixedHeight = imgDim / maxRows
Dim objGraphic As System.Drawing.Image = System.Drawing.Image.FromFile(img)
Dim aspectRatio = objGraphic.Height / objGraphic.Width
Dim reduction = fixedHeight / objGraphic.Height
Dim newwidth = objGraphic.Width * reduction
Image1 = New Bitmap(objGraphic, objGraphic.Width * reduction, fixedHeight)
g.DrawImage(Image1, New Point(left, fixedHeight * rows))
If left >= imgDim Then
rows += 1
left = 0
ElseIf left < imgDim Then
left += newwidth
ElseIf left < imgDim AndAlso rows = maxRows Then
Exit For
End If
End If
Next
Image3.Save(HttpContext.Current.Server.MapPath("/" & fileName), System.Drawing.Imaging.ImageFormat.Jpeg)
Return rootUrl & fileName
g.Dispose()
g = Nothing
Catch ex As Exception
Return ("<P>" & ex.ToString)
End Try
End Function
There's an external function getImageUrl which is simply a series of logic which writes out a directory structure based on the item ID, this is fast so I doubt holding it up at all.
Variables passed into the function are:
idList = a generic List(of String) ' a list of item IDs
imgList = a generic List(of String) ' a list of image names (image names only stored in DB)
head = the Page.Header ' not actually needed in this prototype, but aimed to allow this to write the OG tag to the Page Header
fileName = simply the name to give the generated image file
I can't help thinking that the section from
Dim imageData As System.Drawing.Image = System.Drawing.Image.FromFile(img)
onwards could potentially be sped up. What this does is work out the aspect ratio of the images loaded, then recalculate the size so they're all the same height so that the tile can be neatly populated in tidy rows.
Dim img = Common.getImageUrl(idList(i), imgList(i), "server", "-tb")
This line loads a thumbnail version ("-tb" in last variable) of the images concerned, so all images being dealt with are approx 50x50px so very small to load and determine the aspect ratios from
There are 4 versions of each image stored on my server, in 4 different sizes for fast display on the website, having messed around with the different images loaded here, it seems to make little difference to the script load time.
Is there anything faster than System.Drawing.Image.FromFile(img) that I can use to load in the images and determine the width and height of it?
There are a couple of other situations on my website where I am required to determine the dimensions of an image and then do something with it and all seem a bit slow.
Any advice most welcome!
Just for info, here's an example of an image generated by the above code
http://www.hgtrs.com/recents.jpg
This is generated from the products returned by this page (which might load slowly!):
http://www.hgtrs.com/search.aspx?Type=recents
I should state the my test.aspx file also includes a call to a database, having tested the query used directly, I get a query time of 0.18 seconds, I dont know how this time expands into a real application. I have optimised this query and the tables (it uses one nested query and a join) as much as I know how.