1

I want to implement basic box blur in C#, but in my version change the color of the picture, like a red-green filter. What is the problem with this? Why is doing this? The indexes of rows are good as I think, so I really don't know why to happens this. I want only a simple box blur, so, smooth the image, and nothing else.

this is my original image: https://ibb.co/pPsB2gT

private void boxblur(object sender, EventArgs e)
{
  Bitmap image = new Bitmap(pictureBox.Image);
  BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, image.PixelFormat); 
  int height = bitmapData.Height;
  int bPP = System.Drawing.Bitmap.GetPixelFormatSize(image.PixelFormat) / 8; 
  int width = bitmapData.Width * bPP;

  Bitmap bitmap2 = image.Clone(new Rectangle(0, 0, image.Width, image.Height), image.PixelFormat);
  unsafe 
  {
    byte* firstPixel = (byte*)bitmapData.Scan0; 
    Parallel.For(1, height - 1, j =>
    { 
          byte* line1 = firstPixel + ((j - 1) * bitmapData.Stride); //fst row
          byte* line2 = firstPixel + (j * bitmapData.Stride); //snd row
          byte* line3 = firstPixel + ((j + 1) * bitmapData.Stride); //thrd row
          int idx = 0;
          
          for (int i = 1; i < width-1; i = i + bPP)//one pixel in three elements of a row
          {
            int oldB = line1[i-1] + line1[i+2] + line1[i+5] +
            line2[i-1] + line2[i+2] + line2[i+5] +
            line3[i-1] + line3[i+2] + line3[i+5];

            int oldG = line1[i] + line1[i + 3] + line1[i + 6] +
            line2[i] + line2[i + 3] + line2[i + 6] +
            line3[i] + line3[i + 3] + line3[i + 6];

            int oldR = line1[i+1] + line1[i + 4] + line1[i + 7] +
            line2[i+1] + line2[i + 4] + line2[i + 7] +
            line3[i+1] + line3[i + 4] + line3[i + 7]; ;

            oldB = oldB/9;
            oldG = oldG/9;
            oldR = oldR/9;

            lock (bitmap2)
            {
              bitmap2.SetPixel(idx, j, Color.FromArgb(oldR, oldG, oldB));
            }
            idx++;
          }
          });
    image.UnlockBits(bitmapData);
  }

  pictureBox.Image = bitmap2;           
}

this is how it looks like after algo above: https://ibb.co/dmrQB8F

If I try without parallelism then it works fine:

private void boxblur(object sender, EventArgs e)
{
        Bitmap bitmap = new Bitmap(pictureBox.Image);
        int width = bitmap.Width;
        int height = bitmap.Height;
        Bitmap bitmap2 = bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
        for (int i = 0; i < width; i++)
        {
            for (int j = 0; j < height; j++)
            {
                if (i > 1 && j > 1 && i + 1 != width && j + 1 != height)
                {
                    int newRValue = bitmap.GetPixel(i, j + 1).R +
                                    bitmap.GetPixel(i + 1, j + 1).R +
                                    bitmap.GetPixel(i - 1, j).R +
                                    bitmap.GetPixel(i, j).R +
                                    bitmap.GetPixel(i + 1, j).R +
                                    bitmap.GetPixel(i - 1, j - 1).R +
                                    bitmap.GetPixel(i, j - 1).R +
                                    bitmap.GetPixel(i + 1, j - 1).R;
                    newRValue = Convert.ToInt32(newRValue / 9);

                    int newGValue = bitmap.GetPixel(i, j + 1).G +
                                    bitmap.GetPixel(i + 1, j + 1).G +
                                    bitmap.GetPixel(i - 1, j).G +
                                    bitmap.GetPixel(i, j).G +
                                    bitmap.GetPixel(i + 1, j).G +
                                    bitmap.GetPixel(i - 1, j - 1).G +
                                    bitmap.GetPixel(i, j - 1).G +
                                    bitmap.GetPixel(i + 1, j - 1).G;
                    newGValue = Convert.ToInt32(newGValue / 9);

                    int newBValue = bitmap.GetPixel(i, j + 1).B +
                                    bitmap.GetPixel(i + 1, j + 1).B +
                                    bitmap.GetPixel(i - 1, j).B +
                                    bitmap.GetPixel(i, j).B +
                                    bitmap.GetPixel(i + 1, j).B +
                                    bitmap.GetPixel(i - 1, j - 1).B +
                                    bitmap.GetPixel(i, j - 1).B +
                                    bitmap.GetPixel(i + 1, j - 1).B;
                    newBValue = Convert.ToInt32(newBValue / 9);

                    bitmap2.SetPixel(i, j, Color.FromArgb(newRValue, newGValue, newBValue));
                }
            }
        }
        pictureBox.Image = bitmap2;
}

This is how it look like after algo without parallelism: https://ibb.co/dP9xrzT


I tried to do the Gaussian blur, which works similar as well, but I got the same problem, but now, with another color filtering:

private void gaussianblur(object sender, EventArgs e)
        {
            Bitmap image = new Bitmap(pictureBox.Image);
            BitmapData bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, image.PixelFormat); 
            int height = bitmapData.Height;
            int bPP = System.Drawing.Bitmap.GetPixelFormatSize(image.PixelFormat) / 8; 
            int width = bitmapData.Width * bPP;

            Bitmap bitmap2 = image.Clone(new Rectangle(0, 0, image.Width, image.Height), image.PixelFormat);
            unsafe 
            {
                byte* firstPixel = (byte*)bitmapData.Scan0; 
                Parallel.For(1, height - 1, j =>
                {
                    byte* line1 = firstPixel + ((j - 1) * bitmapData.Stride); 
                    byte* line2 = firstPixel + (j * bitmapData.Stride); 
                    byte* line3 = firstPixel + ((j + 1) * bitmapData.Stride);
                    int idx = 0;

                    for (int i = 1; i < width - 1; i = i + bPP)
                    {
                        int oldB = line1[i - 1] + line1[i + 2] * 2 + line1[i + 5] +
                                   line2[i - 1] *2 + line2[i + 2] *4 + line2[i + 5] * 2 +
                                   line3[i - 1] + line3[i + 2] *2+ line3[i + 5];

                        int oldG = line1[i] + line1[i + 3] *2 + line1[i + 6] +
                                   line2[i] *2+ line2[i + 3] *4+ line2[i + 6] *2+
                                   line3[i] + line3[i + 3] *2+ line3[i + 6];

                        int oldR = line1[i + 1] + line1[i + 4] *2 + line1[i + 7] +
                                   line2[i + 1] *2 + line2[i + 4] *4 + line2[i + 7] *2+
                                   line3[i + 1] + line3[i + 4] *2+ line3[i + 7];

                        oldB = oldB / 16;
                        oldG = oldG / 16;
                        oldR = oldR / 16;

                        lock (bitmap2)
                        {
                            bitmap2.SetPixel(idx, j, Color.FromArgb(oldR, oldG, oldB));
                        }
                        idx++;
                    }
                });
                image.UnlockBits(bitmapData);
            }

            pictureBox.Image = bitmap2;
        }
gabor aron
  • 390
  • 2
  • 3
  • 15
  • Why is `line1[i-1]` the blue pixel? Shouldn’t you index `i`, `i+1` and `i+2` for the three values of a pixel? – Cris Luengo Apr 21 '21 at 01:46
  • @CrisLuengo because I can't calculate the values for the edges, this is why `for` looks like `Parallel.For(1, height - 1,` and `for (int i = 1; i < width-1;`. See the main idea here: https://en.wikipedia.org/wiki/Box_blur#Implementation – gabor aron Apr 21 '21 at 08:02
  • Define "works fine". You have forgotten some directions in your GetPixel variant of boxblur, for instance the R color is making 8 GetPixel calls. Shouldn't it be making 9? In particular, `i - 1, j + 1` is missing. Also, this implementation is needlessly slow, you can make a second pair of loops from -1 to +1 on both x and y axis, and grab each pixel in the box once. – Lasse V. Karlsen Apr 21 '21 at 11:01
  • I also tried your other 2 methods there, I can't see any sign of that red-green filter you're talking about. Granted, I'm slighly red green color blind, but I would assume I would see the same problem in the original input image in this case. They look slightly blurry, which I believe was the point. Can you post a sample input image and output images with the problem so that we can see exactly what you're talking about? – Lasse V. Karlsen Apr 21 '21 at 11:04
  • @LasseV.Karlsen Thank's for the comment, I edited with links to images, what functions generate. – gabor aron Apr 21 '21 at 12:05
  • I presume that the pixel to the left of your pointer is indexed using `i-3`, `i-2`, `i-1`, not `i-1`, `i`, `i+1`. Because of this shift, you’d be swapping colors. – Cris Luengo Apr 21 '21 at 13:53
  • I highly recommend that you put in a much smaller input image, say 10x10, where the three channels have very different information, for example edges at different orientations and locations. Then you’ll be able to clearly see what happens to the data as it goes through your filter. – Cris Luengo Apr 21 '21 at 13:55
  • 1
    _If I try without parallelism then it works fine_ Um, the issue is probably not in the parallelism but in how you access the channels. The 1st version uses Lockbits and tricky offsets in a byte array, the 2nd one the fool-proof GetPixel method. I'm pretty sure you need to look for the error there!. The image is 24bit I presume? – TaW Apr 21 '21 at 15:12
  • @TaW Yes, I thought too, that not parallelism is the main problem here, just I tried to explain what is the difference in the code. The image is a random image loaded from the computer. – gabor aron Apr 21 '21 at 17:56
  • 1
    `Bitmap image = new Bitmap(pictureBox.Image);` this results in a 32bpp image - Your code is assuming 24bits per pixel. Here is the quick [fix](https://stackoverflow.com/questions/2016406/converting-bitmap-pixelformats-in-c-sharp). In code: `Bitmap image = new Bitmap(pictureBox.Image.Width, pictureBox.Image.Height, PixelFormat.Format24bppRgb); using (Graphics gr = Graphics.FromImage(image)) gr.DrawImage(pictureBox.Image, new Rectangle(0, 0, image.Width, image.Height)); ` – TaW Apr 21 '21 at 19:11
  • @TaW Damm, you are right, please give this comment as an answer and then I can accept it as an answer! – gabor aron Apr 21 '21 at 21:37
  • @TaW Where did you know, that my code needs 24bit/pixel? – gabor aron Apr 21 '21 at 21:42
  • The image you posted was a jpg, so that was 24bpp; I just tried and with the fix it worked. Also, I didn't see any handling of the alpha byte. - Sorry, but I don't write answers here any longer. Why not self-answer the question..? – TaW Apr 21 '21 at 22:30
  • Oh and btw: You are using Lockbits and parallel for speed but lose all speed with the SetPixel. Instead use a 2nd lockbits! I have written a few posts that do that. - But for the truly fastest blurring solution use a convolution matrix. This may also be the best solution to the border problem.. – TaW Apr 21 '21 at 23:45
  • @TaW thank's for the ideas! I don't want to answer because You are who solved it! :) – gabor aron Apr 25 '21 at 02:03

0 Answers0