-2

I've been using the Bitmap-class and it's png-ByteStream to deal with images in my WPF-application, mostly with reading/writing files, getting images from native cam libs and showing them in the UI.

Lately I've got some new requirements and tried to upgrade some of that to deal with 64 bit deep images (or 48 bits, I don't need an alpha channel). In pretty much every operation bitmap is converting my image back to 32bppArgb. I've figured out deepcopying and am currently looking into Image.FromStream(), but needing to deal with this got me wondering:

Is there a way to properly deal with non 32bppArgb-images within a C#-application (including true greyscale)? Or does microsoft just neglect the need for it? I've found some related questions, but mostly with 10 year old hacks, so I'd expect there to be a proper way by now...

Edit: As requested, I'm gonna show some code. It's running on .netStandard2.0 to be used mostly inside .net6.0 apps; with System.Drawing.Common on Version 5.0.2 (I don't think it matters but would be willing to upgrade if that's the case). Imagine nativeImage to be a struct from some cameraLib, so we want a deepcopy to get away from the shared memory:

using Bitmap bitmap = new(nativeImage.Width, nativeImage.Height, nativeImage.stride, PixelFormat.Format64bppArgb, nativeImage.DataPtr);
using Bitmap copy = bitmap.DeepCopy(); //custom function

using MemoryStream stream = new();
copy.Save(stream, ImageFormat.Png);
byte[] byteStream = stream.ToArray(); //Saving this to file shows it's really 64 bit

//These bits are stitched together for testing, normally this byte-Array might get passed around quite a bit
using MemoryStream ms = new(byteStream);
using Image image = Image.FromStream(ms, true); //this suddenly is 32bppArgb

with:

public static Bitmap DeepCopy(this Image original)
{
// this just copies the code of the Bitmap(Image)-constructor but uses the original pixelFromat instead of 32bppArgb
    var result = new Bitmap(original.Width, original.Height, original.PixelFormat);
    using (Graphics g = Graphics.FromImage(result))
    {
        g.Clear(Color.Transparent);
        g.DrawImage(original, 0, 0, original.Width, original.Height);
    }
    return result;
}
Simoris
  • 51
  • 4
  • 1
    Please show your current code. Are you using .NET Framework, or .NET Core (with the Drawing package)? – Charlieface Jun 01 '23 at 14:19
  • The graphic mode has to be compatible with the graphic driver on your machine. Does you graphic card support 48 an 64 bits modes. Do you need to upgrade the driver? – jdweng Jun 01 '23 at 14:27
  • @jdweng: Theoretically, that's only relevant for the final display step, not any image processing being done prior. Practically, the .NET libraries may delegate tasks to the video driver, and OP may need to find a driver-independent image manipulation library for doing processing at full color depth. – Ben Voigt Jun 01 '23 at 15:08
  • @BenVoigt : From my experience with windows they are incorrectly tied together. Yes you should be apple to process any graphics on windows, but it seems that only the modes that are supported by the display can be run. It more an installation issue that only drivers that work with the graphic card get enabled. – jdweng Jun 01 '23 at 16:02
  • "only the modes that are supported by the display can be used" *by a wrapper for the display driver*. A real image processing library (which the .NET built-in classes are not) has no trouble. – Ben Voigt Jun 01 '23 at 16:06
  • I'm not that concerned with actually displaying a 64 bit image (for human eye 32 bits are sufficient in my context), I just want to be able to pass them around and into 3rd-party-libs that process them (and need the additional info) and beeing able to check some basic properties (mostly for testing and debugging/tracing). – Simoris Jun 01 '23 at 16:14
  • Instead of messing with `graphics.DrawImage` why not just do `original.Clone(new Rectangle(0, 0, original.Width, original.Height), original.PixelFormat)` For that matter why make a copy altogether? Where does `pic.ImageBytes` come from, how is that relevant to the rest of the code? – Charlieface Jun 01 '23 at 16:15
  • If someone could reccommend or suggest libraries to work with instead of System.Drawing (if that is the problem), I'd also be thankful. – Simoris Jun 01 '23 at 16:16
  • @Charlieface afaik Bitmap.Clone does not copy the underlying data, but just points to it again - something I don't want as that underlying data comes from a native C-lib that may override it later. I've copied to bits of code together and forgot to make that clear /change some names, thanks for pointing it out – Simoris Jun 01 '23 at 16:28
  • So why don't you just pass a byte array around instead? – Charlieface Jun 01 '23 at 16:35
  • I'm passing around a byte array. I do it with png-Bytes instead of "just the pixels", because it also encodes metainformations (like dimensions and pixelformat) and is easy to save/read from disk in a common format. – Simoris Jun 01 '23 at 16:37
  • If you need an imaging library that can create, load, save and process extended pixel format images (48, 64 color and 12, 16, 32 grayscale), you can try the [free evaluation of LEADTOOLS SDK](https://www.leadtools.com/downloads). (Disclosure: I work for its vendor). The SDK has the ability to create images from existing data, in many cases without the need to make a deep copy, which can improve performance greatly. The free evaluation comes with free email, phone and chat support in case you need assistance finding and using the classes and functions suitable for your needs. – Amin Dodin Aug 31 '23 at 20:09

1 Answers1

1

I do not think you will have much success with System.Drawing.Bitmap. In my experience these just have poor support high bit depths. I would instead take a look at the corresponding System.Windows.Media classes. I know PngBitmapEncoder/decoder at least support 16 bit grayscale, but I suspect 48/64 bit works fine as well.

For display I would expect that you would want to do your own tone mapping, since I have never seen any builtin method do a particular good job.

If you want a format for interchange I would suggest creating your own. Any image is essentially represented using just a few properties:

  1. Width
  2. Height
  3. Stride - The number of bytes on a row. This must be at least large enough to fit a row of pixels, but may be larger for alignment reasons.
  4. PixelFormat
  5. Pixel Data - This can essentially anything representing binary data. Byte[], a pointer, a stream, Memory<T> etc. The total size of the data should be Height * Stride.

Most image processing libraries should have methods accepting raw image data. You might need to jump thru some hoops to get it to work, like using unsafe code or doing a blockCopy. You also likely need some mapping code to convert between all the various PixelFormat enums.

Libraries typically provide Bitmap access for convenience, but convert this data to some internal format as soon as possible. You should be able to do the same.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • I know this question is about high bit images, but to account for all pixel formats, including those below 8-bit, the _real_ calculation for the minimum stride is `((BitsPerPixel * width) + 7) / 8`. In general, the graphics system should give you the stride anyway. In .net it's generally rounded up to the next multiple of 32 bits. – Nyerguds Jun 05 '23 at 08:23