问题
I have a scenario where
- choosing a image file and then using BitmapDecoder converting the source to a WriteableBitmap and setting an image.source to the WriteableBitmap.
- Now when a user taps on the image I get the co-ordinates and then want to color the entire region surrounding that pixel with a particular color.(Like the fill option in paint).
The code I've used is
private void setPixelColors(int xCord, int yCord, int newColor)
{
Color color = bit.GetPixel(xCord, yCord);
if (color.R <= 5 && color.G <= 5 && color.B <= 5 || newColor == ConvertColorToInt(color))
{
//Debug.WriteLine("The color was black or same returning");
return;
}
setPixelColors(xCord + 1, yCord, newColor);
setPixelColors(xCord, yCord + 1, newColor);
setPixelColors(xCord - 1, yCord, newColor);
setPixelColors(xCord, yCord - 1, newColor);
//Debug.WriteLine("Setting the color here");
bit.SetPixel(xCord, yCord, newColor);
}
This works but is terribly ineffecient. I'd like to know is there a better method to do this.
Edit: Using the library WriteableBitmapEx.
回答1:
The GetPixel and SetPixel extension methods are very expensive for multiple iterative changes, since they extract the BitmapContext (the WriteableBitmap's PixelBuffer), make the change, and then writes back the updated PixelBuffer when the call is done with the BitmapContext.
WriteableBitmapEx will share the BitmapContext between multiple calls if you get it first and keep a live reference. This will be significantly faster to read out the PixelBuffer only once, make all of the changes, and then write it back only once.
To do this, use WriteableBitmapEx's BitmapContext object (reachable via the GetBitmapContext extension method) to extract the PixelBuffer then call Get and SetPixel as often as needed on the bitmap context. When done with it, Dispose the BitmapContext to save it back into the WriteableBitmap's PixelBuffer (it'll generally be easier to auto-Dispose the BitmapContext via a using statement).
There is sample code that will give the general idea on the WriteableBitmapEx GitHub at https://github.com/teichgraf/WriteableBitmapEx
Something like:
private void setPixelColors(int xCord, int yCord, int newColor)
{
using (bit.GetBitmapContext())
{
_setPixelColors(xCord, yCord, newColor);
}
}
private void _setPixelColors(int xCord, int yCord, int newColor)
{
Color color = bit.GetPixel(xCord, yCord);
if (color.R <= 5 && color.G <= 5 && color.B <= 5 || newColor == ConvertColorToInt(color))
{
//Debug.WriteLine("The color was black or same returning");
return;
}
setPixelColors(xCord + 1, yCord, newColor);
setPixelColors(xCord, yCord + 1, newColor);
setPixelColors(xCord - 1, yCord, newColor);
setPixelColors(xCord, yCord - 1, newColor);
//Debug.WriteLine("Setting the color here");
bit.SetPixel(xCord, yCord, newColor);
}
This should get the overall speed reasonable, but (as Alex suggests) you should look into non-recursive flood fill algorithms, especially if you have large bitmaps. The recursive algorithm will overflow the stack for large fills. There are a few fairly easy options on Wikipedia: https://en.wikipedia.org/wiki/Flood_fill#Alternative_implementations A simple one is to keep basically the same structure you have here but instead of recursing to handle each new pixel, explicitly handle the stack of to-be-edited pixels. If you know you are targeting small areas then that will probably be fast enough on its own. To support larger ones you may want to optimize further.
回答2:
First of all, I can't see how you check if xCord or yCord are out of bitmap boundaries and out of the area you want to fill. Do you know a shape of the tap area you want to fill in advance? For example if it has an elliptic shape, isn't it easier to call FillEllipse instead? Or if rectangular - FillRect?
Second, I believe you recursive algorithm is inefficient. Of course it declines already processed pixels and don't make useless SetPixel calls, but it makes many false checks, because it still gets pixel, analyzes it and produces resursive calls.
Try to visualize it. If you have a bitmap 10x10, and you tap in the middle (5; 5) even before the first pixel would be set (it'd be the pixel at 10;5) you will have 5 recursive calls, and each of them produces another 4 calls and so on. And every call will access to the bitmap, take pixels and spend processor time.
As a slight improvement try to put SetPixel call before recursive calls:
bit.SetPixel(xCord, yCord, newColor);
setPixelColors(xCord + 1, yCord, newColor);
setPixelColors(xCord, yCord + 1, newColor);
setPixelColors(xCord - 1, yCord, newColor);
setPixelColors(xCord, yCord - 1, newColor);
But I think you have to change the whole idea. When working with bitmap, recursive algorithm isn't the best idea.
来源:https://stackoverflow.com/questions/32903841/how-to-use-writeablebitmap-setpixel-efficiently-in-windows-10-uwp