1

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.
Oak_3260548
  • 1,882
  • 3
  • 23
  • 41
  • Use ImageMagick ? – Caius Jard Jun 25 '21 at 08:36
  • ps; 32Gb isn't really relevant - there are upper limits to the size of objects in .NET, unrelated to the physical amount of mem installed – Caius Jard Jun 25 '21 at 08:53
  • @CaiusJard nope :-(. I know and have used ImageMagic, hower I cannot step outside windows standard libraries in this case. – Oak_3260548 Jun 25 '21 at 09:19
  • @CaiusJard you're probably right, I'm not sure which limits apply. – Oak_3260548 Jun 25 '21 at 09:20
  • @Oak_3260548 GDI+ has limitations on image size, and the error messages are not very descriptive. The [System.Windows.Media.Imaging Namespace](https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.imaging) has more capable classes. – Andrew Morton Jun 25 '21 at 09:38
  • @AndrewMorton Thank you, sounds like good. I know about quite some GDI+ limitations from the ton of posts I read, but didn't know about the alternative. I'll investigate how to use System.Windows.Media.Imaging. – Oak_3260548 Jun 25 '21 at 09:41
  • @AndrewMorton, unfortunately it seems System.Windows.Media.Imaging is out of reach because it's WPF. My "journal" (script) is compiled by another software as a module when the feature is executed and is dependant on what the base application has. – Oak_3260548 Jun 25 '21 at 11:09
  • @Oak_3260548 Do the answers to [Can't find System.Windows.Media namespace?](https://stackoverflow.com/q/3154198/1115360) help? – Andrew Morton Jun 25 '21 at 13:34
  • @AndrewMorton Unfortunately no, I quickly found it's a part of PresentationCore.dll. The thing is, that I have no power over what the main application that will compile my vb module at runtime will reference. It's a Qt and .NET Winform application and does not contain WPF libraries. – Oak_3260548 Jun 25 '21 at 13:50

1 Answers1

1

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...
Oak_3260548
  • 1,882
  • 3
  • 23
  • 41