Bitmap/Flood fill: Difference between revisions
Content added Content deleted
m (→{{header|REXX}}: added whitespace.) |
Thundergnat (talk | contribs) (Rename Perl 6 -> Raku, alphabetize, minor clean-up) |
||
Line 8: | Line 8: | ||
[[Image:Unfilledcirc.png|128px|thumb|right]] |
[[Image:Unfilledcirc.png|128px|thumb|right]] |
||
'''Testing''': the basic algorithm is not suitable for ''truecolor'' images; a possible test image is the one shown on the right box; you can try to fill the white area, or the black inner circle. |
'''Testing''': the basic algorithm is not suitable for ''truecolor'' images; a possible test image is the one shown on the right box; you can try to fill the white area, or the black inner circle. |
||
=={{header|Ada}}== |
=={{header|Ada}}== |
||
Line 636: | Line 635: | ||
return 0; |
return 0; |
||
}</lang> |
}</lang> |
||
=={{header|C sharp|C#}}== |
|||
{{works with|C#|3.0}} |
|||
{{libheader|System.Drawing}} |
|||
This implementation matches exact colours only. Since the example image has grey pixels around the edges of the circles, these will remain grey after the interiors are filled. |
|||
<lang csharp> |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
namespace FloodFill |
|||
{ |
|||
class Program |
|||
{ |
|||
private static bool ColorMatch(Color a, Color b) |
|||
{ |
|||
return (a.ToArgb() & 0xffffff) == (b.ToArgb() & 0xffffff); |
|||
} |
|||
static void FloodFill(Bitmap bmp, Point pt, Color targetColor, Color replacementColor) |
|||
{ |
|||
Queue<Point> q = new Queue<Point>(); |
|||
q.Enqueue(pt); |
|||
while (q.Count > 0) |
|||
{ |
|||
Point n = q.Dequeue(); |
|||
if (!ColorMatch(bmp.GetPixel(n.X, n.Y),targetColor)) |
|||
continue; |
|||
Point w = n, e = new Point(n.X + 1, n.Y); |
|||
while ((w.X >= 0) && ColorMatch(bmp.GetPixel(w.X, w.Y),targetColor)) |
|||
{ |
|||
bmp.SetPixel(w.X, w.Y, replacementColor); |
|||
if ((w.Y > 0) && ColorMatch(bmp.GetPixel(w.X, w.Y - 1),targetColor)) |
|||
q.Enqueue(new Point(w.X, w.Y - 1)); |
|||
if ((w.Y < bmp.Height - 1) && ColorMatch(bmp.GetPixel(w.X, w.Y + 1),targetColor)) |
|||
q.Enqueue(new Point(w.X, w.Y + 1)); |
|||
w.X--; |
|||
} |
|||
while ((e.X <= bmp.Width - 1) && ColorMatch(bmp.GetPixel(e.X, e.Y),targetColor)) |
|||
{ |
|||
bmp.SetPixel(e.X, e.Y, replacementColor); |
|||
if ((e.Y > 0) && ColorMatch(bmp.GetPixel(e.X, e.Y - 1), targetColor)) |
|||
q.Enqueue(new Point(e.X, e.Y - 1)); |
|||
if ((e.Y < bmp.Height - 1) && ColorMatch(bmp.GetPixel(e.X, e.Y + 1), targetColor)) |
|||
q.Enqueue(new Point(e.X, e.Y + 1)); |
|||
e.X++; |
|||
} |
|||
} |
|||
} |
|||
static void Main(string[] args) |
|||
{ |
|||
Bitmap bmp = new Bitmap("Unfilledcirc.bmp"); |
|||
FloodFill(bmp, new Point(200, 200), Color.White, Color.Red); |
|||
FloodFill(bmp, new Point(100, 100), Color.Black, Color.Blue); |
|||
bmp.Save("Filledcirc.bmp"); |
|||
} |
|||
} |
|||
} |
|||
</lang> |
|||
=={{header|C++}}== |
=={{header|C++}}== |
||
Line 716: | Line 777: | ||
} |
} |
||
</lang> |
|||
=={{header|C sharp|C#}}== |
|||
{{works with|C#|3.0}} |
|||
{{libheader|System.Drawing}} |
|||
This implementation matches exact colours only. Since the example image has grey pixels around the edges of the circles, these will remain grey after the interiors are filled. |
|||
<lang csharp> |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Drawing; |
|||
namespace FloodFill |
|||
{ |
|||
class Program |
|||
{ |
|||
private static bool ColorMatch(Color a, Color b) |
|||
{ |
|||
return (a.ToArgb() & 0xffffff) == (b.ToArgb() & 0xffffff); |
|||
} |
|||
static void FloodFill(Bitmap bmp, Point pt, Color targetColor, Color replacementColor) |
|||
{ |
|||
Queue<Point> q = new Queue<Point>(); |
|||
q.Enqueue(pt); |
|||
while (q.Count > 0) |
|||
{ |
|||
Point n = q.Dequeue(); |
|||
if (!ColorMatch(bmp.GetPixel(n.X, n.Y),targetColor)) |
|||
continue; |
|||
Point w = n, e = new Point(n.X + 1, n.Y); |
|||
while ((w.X >= 0) && ColorMatch(bmp.GetPixel(w.X, w.Y),targetColor)) |
|||
{ |
|||
bmp.SetPixel(w.X, w.Y, replacementColor); |
|||
if ((w.Y > 0) && ColorMatch(bmp.GetPixel(w.X, w.Y - 1),targetColor)) |
|||
q.Enqueue(new Point(w.X, w.Y - 1)); |
|||
if ((w.Y < bmp.Height - 1) && ColorMatch(bmp.GetPixel(w.X, w.Y + 1),targetColor)) |
|||
q.Enqueue(new Point(w.X, w.Y + 1)); |
|||
w.X--; |
|||
} |
|||
while ((e.X <= bmp.Width - 1) && ColorMatch(bmp.GetPixel(e.X, e.Y),targetColor)) |
|||
{ |
|||
bmp.SetPixel(e.X, e.Y, replacementColor); |
|||
if ((e.Y > 0) && ColorMatch(bmp.GetPixel(e.X, e.Y - 1), targetColor)) |
|||
q.Enqueue(new Point(e.X, e.Y - 1)); |
|||
if ((e.Y < bmp.Height - 1) && ColorMatch(bmp.GetPixel(e.X, e.Y + 1), targetColor)) |
|||
q.Enqueue(new Point(e.X, e.Y + 1)); |
|||
e.X++; |
|||
} |
|||
} |
|||
} |
|||
static void Main(string[] args) |
|||
{ |
|||
Bitmap bmp = new Bitmap("Unfilledcirc.bmp"); |
|||
FloodFill(bmp, new Point(200, 200), Color.White, Color.Red); |
|||
FloodFill(bmp, new Point(100, 100), Color.Black, Color.Blue); |
|||
bmp.Save("Filledcirc.bmp"); |
|||
} |
|||
} |
|||
} |
|||
</lang> |
</lang> |
||
Line 1,224: | Line 1,223: | ||
<lang fortran> call floodfill(animage, point(100,100), rgb(0,0,0), rgb(0,255,0))</lang> |
<lang fortran> call floodfill(animage, point(100,100), rgb(0,0,0), rgb(0,255,0))</lang> |
||
=={{header|FreeBASIC}}== |
=={{header|FreeBASIC}}== |
||
{{trans|BBC BASIC}} |
{{trans|BBC BASIC}} |
||
Line 1,852: | Line 1,852: | ||
This fills better than the Image::Imlib2 <tt>fill</tt> function the inner circle, since because of JPG compression and thanks to the <tt>$distparameter</tt>, it "sees" as black also pixel that are no more exactly black. |
This fills better than the Image::Imlib2 <tt>fill</tt> function the inner circle, since because of JPG compression and thanks to the <tt>$distparameter</tt>, it "sees" as black also pixel that are no more exactly black. |
||
=={{header|Perl 6}}== |
|||
{{works with|Rakudo|2019.11}} |
|||
Using bits and pieces from various other bitmap tasks. |
|||
<lang perl6>class Pixel { has Int ($.R, $.G, $.B) } |
|||
class Bitmap { |
|||
has Int ($.width, $.height); |
|||
has Pixel @.data; |
|||
method pixel( |
|||
$i where ^$!width, |
|||
$j where ^$!height |
|||
--> Pixel |
|||
) is rw { @!data[$i + $j * $!width] } |
|||
} |
|||
role PPM { |
|||
method P6 returns Blob { |
|||
"P6\n{self.width} {self.height}\n255\n".encode('ascii') |
|||
~ Blob.new: flat map { .R, .G, .B }, self.data |
|||
} |
|||
} |
|||
sub load-ppm ( $ppm ) { |
|||
my $fh = $ppm.IO.open( :enc('ISO-8859-1') ); |
|||
my $type = $fh.get; |
|||
my ($width, $height) = $fh.get.split: ' '; |
|||
my $depth = $fh.get; |
|||
Bitmap.new( width => $width.Int, height => $height.Int, |
|||
data => ( $fh.slurp.ords.rotor(3).map: |
|||
{ Pixel.new(R => $_[0], G => $_[1], B => $_[2]) } ) |
|||
) |
|||
} |
|||
sub color-distance (Pixel $c1, Pixel $c2) { |
|||
sqrt( ( ($c1.R - $c2.R)² + ($c1.G - $c2.G)² + ($c1.B - $c2.B)² ) / ( 255 * sqrt(3) ) ); |
|||
} |
|||
sub flood ($img, $x, $y, $c1) { |
|||
my $c2 = $img.pixel($x, $y); |
|||
my $max-distance = 10; |
|||
my @queue; |
|||
my %checked; |
|||
check($x, $y); |
|||
for @queue -> [$x, $y] { |
|||
$img.pixel($x, $y) = $c1.clone; |
|||
} |
|||
sub check ($x, $y) { |
|||
my $c3 = $img.pixel($x, $y); |
|||
if color-distance($c2, $c3) < $max-distance { |
|||
@queue.push: [$x,$y]; |
|||
@queue.elems; |
|||
%checked{"$x,$y"} = 1; |
|||
check($x - 1, $y) if $x > 0 and %checked{"{$x - 1},$y"}:!exists; |
|||
check($x + 1, $y) if $x < $img.width - 1 and %checked{"{$x + 1},$y"}:!exists; |
|||
check($x, $y - 1) if $y > 0 and %checked{"$x,{$y - 1}"}:!exists; |
|||
check($x, $y + 1) if $y < $img.height - 1 and %checked{"$x,{$y + 1}"}:!exists; |
|||
} |
|||
} |
|||
} |
|||
my $infile = './Unfilled-Circle.ppm'; |
|||
my Bitmap $b = load-ppm( $infile ) but PPM; |
|||
flood($b, 5, 5, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 5, 125, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 125, 5, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 125, 125, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 50, 50, Pixel.new(:0R, :0G, :255B)); |
|||
my $outfile = open('./Bitmap-flood-perl6.ppm', :w, :bin); |
|||
$outfile.write: $b.P6; |
|||
</lang> |
|||
See output image [https://github.com/thundergnat/rc/blob/master/img/Bitmap-flood-perl6.png Bitmap-flood-perl6 ] (offsite image file, converted to PNG for ease of viewing) |
|||
=={{header|Phix}}== |
=={{header|Phix}}== |
||
Line 1,961: | Line 1,881: | ||
img = FloodFill(img, 10, 10, green) |
img = FloodFill(img, 10, 10, green) |
||
write_ppm("FloodOut.ppm",img)</lang> |
write_ppm("FloodOut.ppm",img)</lang> |
||
=={{header|PicoLisp}}== |
=={{header|PicoLisp}}== |
||
Line 2,114: | Line 2,033: | ||
img.save( "Filled.png" ) |
img.save( "Filled.png" ) |
||
</lang> |
</lang> |
||
=={{header|R}}== |
=={{header|R}}== |
||
Line 2,270: | Line 2,188: | ||
</lang> |
</lang> |
||
=={{header|Raku}}== |
|||
(formerly Perl 6) |
|||
{{works with|Rakudo|2019.11}} |
|||
Using bits and pieces from various other bitmap tasks. |
|||
<lang perl6>class Pixel { has Int ($.R, $.G, $.B) } |
|||
class Bitmap { |
|||
has Int ($.width, $.height); |
|||
has Pixel @.data; |
|||
method pixel( |
|||
$i where ^$!width, |
|||
$j where ^$!height |
|||
--> Pixel |
|||
) is rw { @!data[$i + $j * $!width] } |
|||
} |
|||
role PPM { |
|||
method P6 returns Blob { |
|||
"P6\n{self.width} {self.height}\n255\n".encode('ascii') |
|||
~ Blob.new: flat map { .R, .G, .B }, self.data |
|||
} |
|||
} |
|||
sub load-ppm ( $ppm ) { |
|||
my $fh = $ppm.IO.open( :enc('ISO-8859-1') ); |
|||
my $type = $fh.get; |
|||
my ($width, $height) = $fh.get.split: ' '; |
|||
my $depth = $fh.get; |
|||
Bitmap.new( width => $width.Int, height => $height.Int, |
|||
data => ( $fh.slurp.ords.rotor(3).map: |
|||
{ Pixel.new(R => $_[0], G => $_[1], B => $_[2]) } ) |
|||
) |
|||
} |
|||
sub color-distance (Pixel $c1, Pixel $c2) { |
|||
sqrt( ( ($c1.R - $c2.R)² + ($c1.G - $c2.G)² + ($c1.B - $c2.B)² ) / ( 255 * sqrt(3) ) ); |
|||
} |
|||
sub flood ($img, $x, $y, $c1) { |
|||
my $c2 = $img.pixel($x, $y); |
|||
my $max-distance = 10; |
|||
my @queue; |
|||
my %checked; |
|||
check($x, $y); |
|||
for @queue -> [$x, $y] { |
|||
$img.pixel($x, $y) = $c1.clone; |
|||
} |
|||
sub check ($x, $y) { |
|||
my $c3 = $img.pixel($x, $y); |
|||
if color-distance($c2, $c3) < $max-distance { |
|||
@queue.push: [$x,$y]; |
|||
@queue.elems; |
|||
%checked{"$x,$y"} = 1; |
|||
check($x - 1, $y) if $x > 0 and %checked{"{$x - 1},$y"}:!exists; |
|||
check($x + 1, $y) if $x < $img.width - 1 and %checked{"{$x + 1},$y"}:!exists; |
|||
check($x, $y - 1) if $y > 0 and %checked{"$x,{$y - 1}"}:!exists; |
|||
check($x, $y + 1) if $y < $img.height - 1 and %checked{"$x,{$y + 1}"}:!exists; |
|||
} |
|||
} |
|||
} |
|||
my $infile = './Unfilled-Circle.ppm'; |
|||
my Bitmap $b = load-ppm( $infile ) but PPM; |
|||
flood($b, 5, 5, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 5, 125, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 125, 5, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 125, 125, Pixel.new(:255R, :0G, :0B)); |
|||
flood($b, 50, 50, Pixel.new(:0R, :0G, :255B)); |
|||
my $outfile = open('./Bitmap-flood-perl6.ppm', :w, :bin); |
|||
$outfile.write: $b.P6; |
|||
</lang> |
|||
See output image [https://github.com/thundergnat/rc/blob/master/img/Bitmap-flood-perl6.png Bitmap-flood-perl6 ] (offsite image file, converted to PNG for ease of viewing) |
|||
=={{header|REXX}}== |
=={{header|REXX}}== |