int21h

Plechovka turbo aneb Floodfill po řádcích

Tento text volně navazuje na minulý článek popisující tu nejjednodušší variantu vyplňovacího algoritmu. Nyní se podíváme na jeho vylepšenou formu, která potřebuje cca tisíckrát méně paměti a navíc je rychlejší.

Vtip je v tom, že místo abychom danou oblast vyplňovali pixel po pixelu a do zásobníku ukládali všechny pixely okolo, kreslíme po řádcích a ukládáme si jen určité "důležité" pixely, kterých je mnohonásobně méně.

Budeme potřebovat úplně stejný zásobník jako minule: nějaké pole nebo cokoli, do kterého budeme strkat souřadnice pixelů na obrazovce (např. array[...] of record x,y:integer end) a pak je z něj stylem LIFO (poslední vložený jde ven první) zase vyndavat. Teoreticky by to vlastně LIFO zásobník být nemusel, fungovalo by to i s FIFO frontou, ale LIFO je jednodušší na naprogramování a líp se s ním pracuje.

Vlastní algoritmus pak vypadá takhle:

  1. Někam si uložíme barvu výchozího bodu, nazvěme si ji VB (kde tato barva končí, tam končí vyplňovaná oblast).
  2. Pokud je tato barva rovna barvě, jakou chceme oblast vybarvit, končíme, protože už je hotovo.
  3. Uložíme na zásobník souřadnice výchozího bodu.
  4. Cyklus:
    1. Vyjmeme ze zásobníku jeden bod (řekněme P):
    2. Od tohoto bodu postupujeme doleva, dokud nenarazíme na okraj oblasti:
    3. Od bodu P postupujeme doprava, dokud nenarazíme na okraj oblasti:
    4. Celý takto nalezený řádek vybarvíme:

    5. Nastavíme se na bod nad bodem P (nazvěme ho třeba Q). Pokud ještě je uvnitř vybarvované oblasti, uložíme tento bod na zásobník:
    6. Od tohoto bodu postupujeme doprava až ke konci vybarveného řádku pod ním. Kdykoli narazíme na přechod zvenčí dovnitř vybarvované oblasti (jeden pixel má barvu jinou než VB a následující ji má rovnou VB), uložíme si jeho souřadnice na zásobník (ukládáme souřadnice toho pixelu s barvou VB):

    7. Od bodu Q postupujeme obdobným způsobem doleva až k levému konci vybarveného řádku:

    8. Nastavíme se na bod pod bodem P (třeba R). Pokud ještě je ve vybarvované oblasti, uložíme tento bod na zásobník:
    9. Postupujeme doprava a doleva obdobně jako před chvílí (u bodu Q):


    10. To celé opakujeme tak dlouho, dokud není zásobník úplně prázdný:






      ...

A to je vše, přátelé.

P.S.: je samozřejmě úplně jedno, jestli budete testovat nejdříve levou stranu a pak pravou nebo naopak. Výše uvedené pořadí bylo zvoleno náhodně.

P.P.S.: je dobré sloučit cykly pro nalezení konce aktuálního řádku a testování řádků nahoře a dole do jednoho (mají stejný index).

P.P.P.S.: když se podmínka "leží pixel uvnitř oblasti a má se vybarvit?" změní z tvaru (barva=původní_barva_oblasti) na tvar (barva<>barva_hranice)and(barva<>barva_kterou_vybarvujeme), změní se tak Plechovka z Paintbrushe na Floodfill z Graph.tpu - barva se přelije přes jakékoli pozadí a zastaví se jedině o zadanou barvu okraje (nebo o barvu, kterou vybarvujeme, aby nevznikla nekonečná smyčka).

Jak je na tom tento algoritmus s rychlostí? O něco lépe než dříve uvedená varianta "bod po bodu do všech stran", protože kreslí celé řádky najednou. Hlavně ale spotřebovává o několik řádů méně paměti: ukládá obvykle méně než 10 bodů na jeden řádek, zatímco předchozí algoritmus by jich potřeboval několik stovek.

Rychlost ale pořád ještě není zrovna špičková. Hlavně proto, že musíme testovat spoustu pixelů (na jednom řádku doleva, doprava a pak to samé o řádek nahoře i dole). První urychlovací zlepšovák, který mě napadl, byl použít místo Getpixelu Getimage, řádky si načíst do paměti celé a testovat je až tam (normální paměť se čte mnohem rychleji než grafická). Jenže zrychlení není moc výrazné a hlavně se tím výrazně zpomalí vyplňování malých oblastí - devadesát Getpixelů je pořád ještě rychlejší než jeden Getimage přes celou šířku obrazovky.

2007-10-10 | Mircosoft
Reklamy: