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?
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...