1

We are using a camera that acquires up to 60 frames per second, providing Bitmaps for us to use in our codebase.

As our wpf-app requires, these bitmaps are scaled based on a scaling factor; That scaling-process is by far the most limiting factor when it comes to actually displaying 60 fps. I am aware of new Bitmap(Bitmap source, int width, int height) which is obviously the simplest way to resize a Bitmap;

Nevertheless, I am trying to implement a "manual" approach using BitmapData and pointers. I have come up with the following:

public static Bitmap /*myMoBetta*/ResizeBitmap(this Bitmap bmp, double scaleFactor)
        {
            int desiredWidth = (int)(bmp.Width * scaleFactor),
                desiredHeight = (int)(bmp.Height * scaleFactor);

            var scaled = new Bitmap(desiredWidth, desiredHeight, bmp.PixelFormat);

            int formatSize = (int)Math.Ceiling(Image.GetPixelFormatSize(bmp.PixelFormat)/8.0);

            BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
            BitmapData scaledData = scaled.LockBits(new Rectangle(0, 0, scaled.Width, scaled.Height), ImageLockMode.WriteOnly, scaled.PixelFormat);

            unsafe
            {
                var srcPtr = (byte*)bmpData.Scan0.ToPointer();
                var destPtr = (byte*)scaledData.Scan0.ToPointer();

                int scaledDataSize = scaledData.Stride * scaledData.Height;
                int nextPixel = (int)(1 / scaleFactor)*formatSize;

                Parallel.For(0, scaledDataSize - formatSize,
                    i =>
                    {
                        for (int j = 0; j < formatSize; j++)
                        {
                            destPtr[i + j] = srcPtr[i * nextPixel + j];
                        }
                    });
            }

            bmp.UnlockBits(bmpData);
            bmp.Dispose();
            scaled.UnlockBits(scaledData);

            return scaled;
        }

Given scalingFactor < 1. Actually using this algorithm does not seem to work, though. How are the bits of each pixel arranged in memory, exactly? My guess was that calling Image.GetPixelFormatSize() and deviding its result by 8 returns the number of bytes per pixel; But continuing to copy only formatSize amout of bytes every 1 / scaleFactor * formatSize byte results in a corrupted image.

What am I missing?

sir_photch
  • 25
  • 6
  • Maybe you haven't noticed, but when your original Bitmap's `PixelFormat` is, e.g., `Format24bppRgb` and you create a new one with, e.g., `var resized = new Bitmap(original, 100, 100)`, the new Image `PixelFormat` is now `Format32bppArgb`. Why is that? -- Read the *Important notes about the Stride* here: [Analyze colors of an Image](https://stackoverflow.com/a/59102380/7444103). -- Since you have a WPF app, why are you using GDI+ to scale bitmaps? – Jimi Aug 07 '21 at 18:33
  • https://www.google.com/search?q=stack+overflow+c%23+resize+image –  Aug 07 '21 at 18:35
  • @Jimi very insightful, I actually think that the padding with zeroes is an issue in this case. Regarding GDI+, `new Bitmap(original, width, height)` calls graphics.drawimage which in turn calls gdi+ externally. (https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Bitmap.cs,324 | https://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Graphics.cs,2841) – sir_photch Aug 08 '21 at 15:29
  • @OlivierRogier most answers to be found online just refer to GDI+ – sir_photch Aug 08 '21 at 15:38
  • Padding is required because of hardware *alignment* (always 4-bytes aligned). -- I was asking why do you use GDI+ when you have a WPF app. Other tools are used in this platform), GDI+ is common in WinForms (since its Controls can use Bitmap object, WPF's cannot). – Jimi Aug 08 '21 at 18:18

1 Answers1

0

After some more research I bumped into OpenCV that has it's own .NET implementation with Emgu.CV, containing relevant methods for faster resizing.

My ResizeBitmap()-function has shrinked significantly:

public static Bitmap ResizeBitmap(this Bitmap bmp, int width, int height)
    {
        var desiredSize = new Size(width, height);
        var src = new Emgu.CV.Image<Rgb, byte>(bmp);
        var dest = new Emgu.CV.Image<Rgb, byte>(desiredSize);

        Emgu.CV.CvInvoke.Resize(src, dest, desiredSize);

        bmp.Dispose();
        src.Dispose();

        return dest.ToBitmap();
    }

I have not tested performance thouroughly, but while debugging, this implementation reduced executiontime from 22ms with new Bitmap(source, width, height) to about 7ms.

sir_photch
  • 25
  • 6