Resizing a Bitmap manually

318 views Asked by At

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?

1

There are 1 answers

0
sir_photch On BEST ANSWER

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.