• Aucun résultat trouvé

Fill Algorithms

Dans le document Computer Graphics and Geometric Modeling (Page 44-51)

Contour-filling algorithms are used in many places. For example, in pattern recogni-tion problems integrals may have to be computed over areas; in photo typesetting, fonts are described by contours that are later filled; in animation, the cel painter who fills figures has the next most time-consuming job after the animator.

There are two broad classes of such algorithms – polygon-based(edge-filling) algo-rithms and pixel-based algorithms. The former can be used in the case where the regions to be filled are defined by polygons and we can use the equations for the edges.

The latter are, in a sense, more general because they can be used both for polygonal regions and also arbitrary regions whose boundaries are defined on the pixel level.

There is also a distinction as to how the algorithm decides whether a point is in the interior of a region. Some use a parity checkthat is based on the fact that lines intersect a closed curve an even number of times (if one counts intersections at certain special points such as at points of tangency correctly). This test is always used in case of polygon-based algorithms, but can also be used for pixel-based ones. Other algo-rithms, called seed fillalgorithms, use connectivity methods. Here it is assumed that one is given a starting point or seed. Then one sees which pixels can be reached from this one without crossing the contour. The bounding curves can be quite general. This approach applies only to pixel-based algorithms. Also, one needs to know an interior point. This is okay in interactive situations (where one picks one using a mouse, for example), but if one wants to automate the process, note how border-following algo-rithms become relevant.

In this section we shall describe the pixel-based seed fill algorithms. Section 2.9.1 will look at polygon-based fill algorithms.

The Flood Fill Problem: Givendistinctcolors c and c¢, a set Aof the same color c bounded by points whose colors are differentfrom c and c¢, find an algorithm that changes all points ofAandonlythose to the color c¢.

An algorithm that solves this problem is called a flood fill algorithm. There are a number of related fill problems and associated algorithms. For example, boundary fill algorithms assume that all points of the boundary have the samecolor, which is

dif-ferentfrom the color inside the region, where the boundaryof a set S means here the set of points of Scthat are adjacent to S.

In the algorithms of this section, the Boolean-valued function Inside(x,y) deter-mines whether or not the pixel at (x,y) has the property one wants. The procedure Set(x,y) sets the value of the pixel at (x,y) to its desired value. For example, to get a flood fill algorithm let Inside(x,y) be true if the value of the pixel at (x,y) agrees with the value of the pixels in the region and let Set(x,y) set the pixel value to its new value (the same as Draw(x,y,c¢)). Using the functions Inside and Setwill make our algorithms more general and applicable to a variety of fill algorithms. There is one constraint on the Inside function however: Inside(x,y) must return false after an operationSet(x,y).

Assume 4-adjacency is chosen and that our regions are 4-connected. The BFA pro-cedure in Algorithm 2.4.1 shows that the basic idea behind a fill algorithm is very simple. Notice that 4-connected is important and that the algorithm will not work if the region is not 4-connected.

Although the BFA algorithm is simple, the recursion is expensive. One of the earli-est nonrecursive algorithms is due to Smith ([Smit79]). It is not very efficient because pixels are visited twice, but many of the better algorithms are based on it. It will be worthwhile to describe Smith’s algorithm, Algorithm 2.4.2, first before we present the one due to [Fish90b]. In this algorithm and the next, the constants XMIN, XMAX, YMIN, and YMAX define the minimum and maximum values for the x- and y-coordinates of pixels in the viewport. The procedures PushandPoppush and pop a pair (x,y) onto and from a stack, respectively. The function StackNotEmptytests whether this stack is empty or not. The procedures InsideandSetare as described above.

For example, suppose that in Figure 2.5 our starting point is (7,3). After the first FillRightcommand the two-pixel segment from (7,3) to (8,3) would have been filled.

TheFillLeftcommand would fill (6,3). The ScanHicommand would place the pixel coordinates (6,4) and (8,4) on the stack in that order. The ScanLocommand would add (6,2). The segments of the region that (6,4), (8,4), and (6,2) belong to are usually called “shadows.” The point of the ScanHi and ScanLo procedures is to find these shadows that still need to be filled. We now return to the beginning of the main while loop, pop (6,2), and make that our new starting point. The next FillRight and FillLeft would fill the segment from (2,2) to (8,2). The ScanHi and ScanLo would

procedure BFA (integer x, y) if Inside (x,y) then

begin Set (x,y);

BFA (x,y- 1); BFA (x,y + 1);

BFA (x- 1,y); BFA (x + 1,y);

end;

Algorithm 2.4.1. The basic fill algorithm.

{ Global variables } integer x, y, lx, rx;

a stack of pixel coordinates (x,y);

procedure Fill (integer seedx, seedy) begin

x := seedx; y := seedy;

if not (Inside (x,y)) then Exit;

Push (x,y);

while StackNotEmpty () do begin

PopXY ();

if Inside (x,y) then begin

FillRight (); FillLeft (); { Fill segment containing pixel }

ScanHi (); ScanLo (); { Scan above and below current segment } end

end end;

procedure FillRight () begin

integer tx;

tx := x;

{ Move right setting all pixels of segment as we go } while Inside (tx,y) and (tx £ XMAX) do

begin

Set (tx,y); tx := tx+ 1;

end;

rx := tx- 1; { Save index of right most pixel in segment } end;

procedure FillLeft () begin

integer tx;

tx := x;

{ Move left setting all pixels of segment as we go } while Inside (tx,y)and (tx ≥ XMIN) do

begin

Set (tx,y); tx := tx- 1;

end;

lx := tx+ 1; { Save index of left most pixel in segment } end;

Algorithm 2.4.2. The Smith seed fill algorithm.

procedure ScanHi ()

{ Scan the pixels between lx and rx in the scan line above the current one.

Stack the left most of these for any segment of our region that we find.

We do not set any pixels in this pass. } begin

integer tx;

if y+ 1 > YMAX then Exit;

tx := lx;

while tx£ rx do begin

{ Scan past any pixels not in region }

while not (Inside (tx,y + 1)) and (tx£ rx) do tx := tx+ 1;

if tx£ rx then begin

Push (tx,y + 1);

{ We just saved the first point of a segment in region.

Now scan past the rest of the pixels in this segment. } while Inside (tx,y + 1)and (tx £ rx) do tx := tx+ 1;

end;

end end;

procedure ScanLo ()

{ Scan the pixels between lx and rx in the scan line below the current one.

Stack the left most of these for any segment of our region that we find.

We do not set any pixels in this pass. } begin

integer tx;

if y- 1 < YMIN then Exit;

tx := lx;

while tx£ rx do begin

{ Scan past any pixels not in region }

while not (Inside (tx,y - 1)) and (tx £ rx) do tx := tx+ 1;

if tx£ rx then begin

Push (tx,y - 1);

{ We just saved the first point of a segment in region.

Now scan past the rest of the pixels in this segment. } while Inside (tx,y - 1)and (tx £ rx) do tx := tx+ 1;

end;

end end;

Algorithm 2.4.2. Continued

put (2,3) and (6,3) on the stack. The loop would start over and pop (6,3). This time, since (6,3) has already been filled, we immediately jump back to the beginning and pop (2,3), and so on.

The problem with Smith’s basic algorithm is that we look at some pixels twice, as we saw in the case of (2,3) in the previous example. This happens because we auto-matically put coordinates from both the line above and the line below the current one on the stack. When we then, say, deal with the line above, the algorithm will have us look at the current line again because it will be the line below that one. For a fast algorithm we need to prevent this duplicate effort. Algorithm 2.4.3 from [Fish90b]

involves more bookkeeping because it differentiates between the three different types of possible shadows shown in Figure 2.6, but it will read each pixel only slightly more than once on the average and also has good worst-case behavior. Fishkin points out that it is optimal if the region has no holes.

An alternative improvement to Smith’s seed fill algorithm is described by Heck-bert in [Heck90b].

Finally, another distinction that is made between flood fill algorithms is whether we are dealing with hard orsoftarea flooding. The algorithms we have described so far were hard area flooding, which basically assumed that the region to be filled was demarcated by a “solid” boundary, that is, a curve of pixels all of the same color. Such a boundary would be a jagged curve. To get a smoother looking boundary one typi-cally would blur or “shade” it by assigning a gradation of colors in a neighborhood of it. (The causes of the “jaggies” and solutions to the aliasing problem are discussed later in Section 2.6.) If boundaries are shaded, then we would like filling algorithms to maintain this shading. Soft area flooding refers to algorithms that do this and leave any “shading” intact. Smith’s paper [Smit79] is a good reference for both hard and soft area flooding. The tint fillalgorithm he describes in that paper is a soft area flood-ing algorithm.

There are other types of pixel-based fill algorithms. Pavlidis [Pavl82] describes a parity check type algorithm. Rogers [Roge98] describes various algorithms for filling regions bounded by polygons that he calls “edge fill” algorithms.

Figure 2.5. A fill algorithm example.

Figure 2.6. Pixel shadows.

direction = (-1,+1);

stackRec = record { a stackRec records the data for one shadow } integer myLx, myRx, { endpoints of this shadow }

dadLx, dadRx, { endpoints of my parent } myY; { scan line of shadow }

direction myDirection; { -1 means below parent, +1 means above } end;

{ Global variable }

stack of stackRec shadowStack;

procedure Fill (integer seedx, seedy) begin

label 1, 2;

integer x, lx, rx, dadLx, dadRx, y;

direction dir;

boolean wasIn;

Initialize shadowStack to empty;

Find the span [lx,rx] containing the seed point;

Push (lx,rx,lx,rx,seedy+1,1);

Push (lx,rx,lx,rx,seedy-1,-1);

while StackNotEmpty () do begin

1: Pop ();

if (y < YMIN) or (y > YMAX) then Goto 1;

x := lx+ 1;

wasIn := Inside (lx,y);

if wasIn then begin

Set (lx,y); lx := lx-1;

{ If the left edge of the shadow touches a span, then move to its left end setting pixels as we go }

while Inside (lx,y)and lx≥ XMIN do begin

Set (lx,y); lx := lx-1;

end end;

{ Start the main loop. Moving to the right starting from the current position x, if wasIn is true, then we are inside a span whose left edge is at lx. }

Algorithm 2.4.3. The Fishkin seed fill algorithm.

while x£ XMAX do if wasIn

then begin

if Inside (x,y)

then Set (x,y) { was inside and still inside } else

begin

{ was inside but not anymore, i.e., we just passed the right edge of a span } Stack (dadLx,dadRx,lx,x-1,y,dir);

wasIn :=false;

end end

else begin

if x > rx then Goto 2;

if Inside (x,y) then begin

{ we weren't inside but are now, i.e., we just found the left edge of a new span } Set (x,y);

wasIn :=true;

lx := x;

end x := x +1;

end;

2: if wasIn then

{ we just hit the edge of the viewport while in a span } Stack (dadLx,dadRx,lx,x-1,y,dir);

end end;

boolean function StackNotEmpty ()

{ Returns trueif shadowStack is empty and falseotherwise } procedure Push (integer myl, myr, dadl, dadr, y;directiondir) { Pushes record onto shadowStack }

procedure Pop ()

{ Pops top of shadowStack into local variables lx, rx, dadLx, dadRx, y, dir } procedure Stack (integerdadLx, dadRx, lx, rx, y;directiondir)

{ Pushes an extra shadow onto shadowStack, given a newly discovered span and its parent. This is where the three types of shadows are differentiated. } begin

Algorithm 2.4.3. Continued

Dans le document Computer Graphics and Geometric Modeling (Page 44-51)