How to Fill Part of Image with Color

How can i fill part of image with color?

To floodfill an area you need a foodfill routine and very little else.

See this example:

Sample Image

It uses two pictureboxes, also a label to display the chosen color.

And two mouse click events, one to pick the color:

private void pictureBoxPalette_MouseClick(object sender, MouseEventArgs e)
{
Point sPt = scaledPoint(pictureBoxPalette, e.Location);
lbl_color.BackColor = ((Bitmap)pictureBoxPalette.Image).GetPixel(sPt.X, sPt.Y);
}

..and one to call the fill:

private void pictureBoxTgt_MouseClick(object sender, MouseEventArgs e)
{
Point sPt = scaledPoint(pictureBoxTgt, e.Location);
Bitmap bmp = (Bitmap)pictureBoxTgt.Image;
Color c0 = bmp.GetPixel(sPt.X, sPt.Y);
Fill4(bmp, sPt, c0, lbl_color.BackColor);
pictureBoxTgt.Image = bmp;
}

The Floodfill routine is taken from this post; it is basically a direct implementation of a wikipedia algorithm..:

static void Fill4(Bitmap bmp, Point pt, Color c0, Color c1)
{
Color cx = bmp.GetPixel(pt.X, pt.Y);
if (cx.GetBrightness() < 0.01f) return; // optional, to prevent filling a black grid
Rectangle bmpRect = new Rectangle(Point.Empty, bmp.Size);
Stack<Point> stack = new Stack<Point>();
int x0 = pt.X;
int y0 = pt.Y;

stack.Push(new Point(x0, y0) );
while (stack.Any() )
{
Point p = stack.Pop();
if (!bmpRect.Contains(p)) continue;
cx = bmp.GetPixel(p.X, p.Y);
if (cx.ToArgb() == c0.ToArgb()) //*
{
bmp.SetPixel(p.X, p.Y, c1);
stack.Push(new Point(p.X, p.Y + 1));
stack.Push(new Point(p.X, p.Y - 1));
stack.Push(new Point(p.X + 1, p.Y));
stack.Push(new Point(p.X - 1, p.Y));
}
}
}

Note: (*) Color equality will fail if one of the colors is a known or named color. So we need to convert to a common format..

Update

I have updated the code to include a function that will scale a mouse click location to an image pixel point; now it will work with SizeMode=StretchImage as well, so you can work on the whole image..

static Point scaledPoint(PictureBox pb, Point pt)
{
float scaleX = 1f * pb.Image.Width / pb.ClientSize.Width;
float scaleY = 1f * pb.Image.Height / pb.ClientSize.Height;
return new Point((int)(pt.X * scaleX), (int)(pt.Y * scaleY));
}

Of course you can then save the Image.

Note that your original image is 4bpp and must be converted to 24bpp or better before coloring..

Also note that for SizeMode=Zoom the calculations are a little more involved. Here is an example that should work with any SizeMode.:

static Point scaledPoint(PictureBox pbox, Point pt)
{
Size si = pbox.Image.Size;
Size sp = pbox.ClientSize;
int left = 0;
int top = 0;

if (pbox.SizeMode == PictureBoxSizeMode.Normal ||
pbox.SizeMode == PictureBoxSizeMode.AutoSize) return pt;
if (pbox.SizeMode == PictureBoxSizeMode.CenterImage)
{
left = (sp.Width - si.Width) / 2;
top = (sp.Height - si.Height) / 2;
return new Point(pt.X - left, pt.Y - top);
}
if (pbox.SizeMode == PictureBoxSizeMode.Zoom)
{
if (1f * si.Width / si.Height < 1f * sp.Width / sp.Height)
left = (sp.Width - si.Width * sp.Height / si.Height) / 2;
else
top = (sp.Height - si.Height * sp.Width / si.Width) / 2;
}

pt = new Point(pt.X - left, pt.Y - top);
float scaleX = 1f * pbox.Image.Width / (pbox.ClientSize.Width - 2 * left) ;
float scaleY = 1f * pbox.Image.Height / (pbox.ClientSize.Height - 2 * top);
return new Point((int)(pt.X * scaleX), (int)(pt.Y * scaleY));
}

change particular area of an image and fill color in that area

Demo:

Full page: https://angularjs-rj88cu.stackblitz.io/

Code: https://stackblitz.com/edit/angularjs-rj88cu

Explanation:

The editor is an svg containing background image as <image> and areas as <path> both are created from editor.imageUrl and editor.areas

For downloading,

  1. Background image is drawn on a canvas
  2. SVG (without <image>) is converted to dataUrl and then drawn on canvas
  3. Canvas is converted to dataUrl for downloading

Why canvas was not used instead of svg in the first place?

Because mouse interaction are difficult to implement in canvas and is much easier in inline svg as the work like DOM elements (hover pseudo class, click events, etc)

Also I assumed you wanted angularjs because you have tagged it (even though you have not mentioned in the question)

Also there are a lot of bad practices in the code like editor not being a component and styling input[type=color]'s Shadow DOM (you can use some colorpicker plugin instead)

PS: Tell me if something needs to be changed

how to fill color in image in particular area?

I found the Solution with Flood fill algoritham

private void FloodFill(Bitmap bmp, Point pt, int targetColor, int replacementColor){
Queue<Point> q = new LinkedList<Point>();
q.add(pt);
while (q.size() > 0) {
Point n = q.poll();
if (bmp.getPixel(n.x, n.y) != targetColor)
continue;

Point w = n, e = new Point(n.x + 1, n.y);
while ((w.x > 0) && (bmp.getPixel(w.x, w.y) == targetColor)) {
bmp.setPixel(w.x, w.y, replacementColor);
if ((w.y > 0) && (bmp.getPixel(w.x, w.y - 1) == targetColor))
q.add(new Point(w.x, w.y - 1));
if ((w.y < bmp.getHeight() - 1)
&& (bmp.getPixel(w.x, w.y + 1) == targetColor))
q.add(new Point(w.x, w.y + 1));
w.x--;
}
while ((e.x < bmp.getWidth() - 1)
&& (bmp.getPixel(e.x, e.y) == targetColor)) {
bmp.setPixel(e.x, e.y, replacementColor);

if ((e.y > 0) && (bmp.getPixel(e.x, e.y - 1) == targetColor))
q.add(new Point(e.x, e.y - 1));
if ((e.y < bmp.getHeight() - 1)
&& (bmp.getPixel(e.x, e.y + 1) == targetColor))
q.add(new Point(e.x, e.y + 1));
e.x++;
}
}}

flood fill in android:

See this FloodFill

how to fill the transparent part of an RGBA image with color, given the xy touch position?

cv2 has cv2.floodFill()

It will get color in start_point and search the same color in neighborhood and replace with color

import cv2

img_before = cv2.imread('image.png')

img_after = img_before.copy()

start_point = (400, 300)
color = (128, 128, 128)
cv2.floodFill(img_after, None, start_point, color)

cv2.imshow('before', img_before)
cv2.imshow('after', img_after)
cv2.waitKey(0)

cv2.destroyAllWindows()

but it works only with RGB but not RBGA so it would need more work.


pillow also has ImageDraw.floodfill() and it work with RGBA. If you work with numpy.array then it will need only to convert from array to Image and later back to array.

from PIL import Image, ImageDraw

img_before = Image.open('image.png')

img_after = img_before.copy()

start_point = (400, 300)
color = (128, 128, 128, 255)
img_draw = ImageDraw.floodfill(img_after, start_point, color, thresh=50)

img_before.show()
img_after.show()

#img_after.save('image_after.png')

Fill complex image with color

You could make a function that loops through all the pixels and fill them with black color.

BufferedImage image = ...
Color fillColor = new Color(0, 0, 0); // Black

for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int color = image.getRGB(x, y);
int alpha = (color >> 24) & 0xff;

if (alpha == 255) {
image.setRGB(x, y, fillColor.getRGB());
}
}
}

Obviously, this will only work on fully opaque pixels. If the image on top contains some transparency, you can also change the condition to be more tolerant: if (alpha > 127). This will fill all pixels that are less than 50% transparent.



Related Topics



Leave a reply



Submit