10. Skener, bitmapy


Posledná zmena: 23.10.2002

Banner Text 22.10.2002

    čo sme sa doteraz naučili

    • keď spracovávame zložitejší textový súbor (napr. pascalovský program), môžeme čítať buď po znakoch alebo prečítať celý riadok naraz
    • súbor s obrázkom (s príponou .BMP) môžeme pomocou LoadFromFile prečítať do grafickej plochy

    čo sa budeme dnes učiť

    • textové súbory so zložitejšou štruktúrou je niekedy výhodné predspracovať lexikálnou analýzou
    • v programe môžeme manipulovať aj s obrázkami, môžeme ich napríklad "opečiatkovať" do grafickej plochy

Lexikálna analýza - skener (scanner)

  • algoritmus, ktorý slúži na predspracovanie textového vstupu - rozloží text na logické časti, tzv. lexikálne jednotky - lexémy
  • najlepšie sa využije pri spracovaní zdrojových textov programov, najčastejšie v pascale:
    • lexikálnymi jednotkami by teraz mohli byť, napr. identifikátor (vrátane rezervované slová), číselná konštanta, konštanta znakový reťazec, špeciálne symboly, napr. zátvorky, bodkočiarka, čiarka, relačné a aritmetické operátory a pod.
    • pomocou takéhoto algoritmu môžeme veľmi jednoducho spracovať identifikátory premenných alebo procedúr, rôznym spôsobom vypisovať pascalovský program, farebne vyznačovať niektoré časti a pod.
    • princíp práce je tento: procedúra skener, vždy keď je zavolaná, zanalyzuje ďalšiu časť textu a v globálnych premenných vráti nasledujúcu lexému - typ lexémy + doplnková informácia lexémy (hodnota pre číslo alebo reťazec, identifikátor, ...)
    • po spracovaní každej lexémy skener v textovom súbore skončí nastavený na znaku tesne za lexémou - je jasné, že vždy, keď je zavolaný, už musí počítať s tým, že je možno nastavený na prvom znaku nejakej lexémy (alebo napr. na medzere pred ňou)
    • niektoré reťazce sú pri spracovávaní textu nedôležité a preto ich môže skener filtrovať, napr. medzery a konce riadkov a tiež všetky typy komentárov {...}, //..., (*...*) - často slúžia len ako oddeľovače lexém
  • zhrnieme pravidlá pre vytvorenie skenera:
    • určíme, aké rôzne typy lexém budeme zo súboru zisťovať - zadefinujeme globálnu premennú vymenovaného typu:
         lex: (koniec,ident,cislo,retazec,symbol,...);
    • vytvoríme pomocnú procedúru znak, ktorá číta súbor a do globálnej znakovej premennej z priradí načítaný znak:
      • #0, keď je koniec súboru (mohol by sa použiť aj iný znak, napr. #26)
      • #1, na konci riadka, pričom spracuje tento koniec riadka (aj tu by mohol byť iný znak, napr. #13)
      • inak prečíta znak zo súboru
  • z:char aj t:TextFile sú globálne premenné:

procedúra znak:

var
  z:char;
  t:TextFile;

procedure znak;   // procedúra na čítanie zo súboru
begin
  if eof(t) then z:=#0
  else if eoln(t) then begin z:=#1; readln(t); end
  else read(t,z);
end;
  • vytvoríme procedúru skener, ktorá spracováva znaky a stará sa o tie postupnosti znakov, ktoré tvoria nejaké lexémy (každé volanie zistí nasledujúcu lexému)

procedúra skener:

procedure skener;
var
  ok:boolean;
begin
  ok:=false;
  repeat
    case z of
      #0:                  // už je koniec súboru
        begin
          lex:=koniec; ok:=true;  
        end;
      '{':                 // začína komentár
        begin
          // nájdeme k nemu druhú zátvorku alebo prídeme na koniec súboru
        end;
      '''':                // začína reťazec
      'A'..'Z','a'..'z':   // začína identifikátor
      ...
      else ...
    end;   // case
  until ok;
end;
  • ok - znamená, že sme našli lexému - môžeme ukončiť toto volanie => v lex je typ lexémy v iných premenných je ďalšia informácia (napr. reťazec identifikátora premennej)

v tele samotného programu sa nachádza otvorenie súboru, inicializácia a hlavný cyklus:

  znak; skener;
  while lex<>koniec do begin
    // spracovanie tých lexém, ktoré potrebujeme
    skener;
  end;

Príklad

Máme daný pascalovský unit "unit1.pas" - zistíme, či ku každému begin prislúcha jeden end - nakoľko v programoch môžu byť aj iné konštrukcie, ktorých súčasťou je end, musíme počítať aj s nimi: record, case a class - taktiež na konci unitu je jeden end navyše. Použijeme takýto algoritmus: pri každom begin, record ... pripočítame 1 a pri end odpočítame 1 - ak momentálny súčet bude menší ako 0, znamená, že práve bolo viac end ako "otvorených" begin - po skončení analýzy musí byť súčet -1, lebo na záver je jedno end s bodkou.

  • skener nepotrebuje vrátiť všetky typy lexém - stačí nám len ident - identifikátor
  • budeme filtrovať komentáre {...} a //... a tiež konštanty znakové reťazce, lebo by mohli obsahovať napr. slovo end a takéto by sme nemali započítať

zjednodušený skener:

var
  lex:(koniec,ident);
  hodn:string;

procedure skener;
var
  ok:boolean;
begin
  ok:=false;
  repeat
    case z of
      #0:
        begin
          lex:=koniec; ok:=true; 
        end;
      '{':
        begin
          repeat znak until z in ['}',#0];
          znak;
        end;
      '/':
        begin
          znak;
          if z='/' then begin
            repeat znak until z in [#1,#0];
          end;
        end;
      '''':
        begin
          znak;
          while not (z in ['''',#0,#1]) do znak;
          if z='''' then znak;
        end;
      'A'..'Z','a'..'z','_':
        begin
          hodn:=''; lex:=ident; ok:=true;
          while z in ['A'..'Z','a'..'z','0'..'9','_'] do begin
            hodn:=hodn+z; znak;
          end;
        end;
      else
        znak;
    end;
  until ok;
end;
  • program, ktorý spracováva text, dostáva od skenera len 2 typy lexém: koniec a ident, preto v tele cyklu je jasné, že lexéma je ident

spracovanie textu:

procedure TForm1.Button1Click(Sender: TObject);
var
  poc:integer;
begin
//  Memo1.Lines.LoadFromFile('unit1.pas');
//  Memo1.Lines.Add('===================================================');
  AssignFile(t,'unit1.pas'); Reset(t);
  znak; skener; poc:=0;
  while lex<>koniec do begin
    hodn:=LowerCase(hodn);
    if (hodn='begin') or (hodn='case') or
       (hodn='record') or (hodn='class') then begin
      if poc<0 then Memo1.Lines.Add('zle je');
      inc(poc);
//    Memo1.Lines.Add('... '+hodn+' ... '+IntToStr(poc));
    end
    else if hodn='end' then begin
      dec(poc);
//    Memo1.Lines.Add('... '+hodn+' ... '+IntToStr(poc));
    end;
    skener;
  end;
  CloseFile(t);
  if poc<>-1 then
    Memo1.Lines.Add('zle je    pocet = '+IntToStr(poc))
  else
    Memo1.Lines.Add('ok');
end;
  • pouvažujte nad tým, čo by bolo treba zmeniť, aby sme vedeli hlásiť chybu pri ľubovoľnom texte za záverečným end, za ktorým je bodka

Príklad

Textový súbor unit2.pas obsahuje pascalovský program - treba v ňom nahradiť všetky výskyty identifikátora r1 reťazcom r2 - samozrejme, že nahrádzať sa nebude ani v reťazcoch ani v komentároch.

  • zadefinujeme pomocnú procedúru pis, ktorá vypíše znak z do výstupného súboru t1

procedúra pis:

var
  t1:TextFile;

procedure pis;
begin
  if z<>#0 then
    if z=#1 then writeln(t1)
    else write(t1,z);
end;
  • všimnite si procedúru skener - komentáre aj reťazce sa len kopírujú zo vstupu na výstup (znak => pis), podobne sa kopírujú aj všetky symboly (aj čísla), ktoré nie sú súčasťou identifikátorov - skener rozpozná len identifikátory a samozrejme aj koniec vstupu

procedúra skener:

var
  lex:(koniec,ident);
  hodn:string;

procedure skener;
var
  ok:boolean;
begin
  ok:=false;
  repeat
    case z of
      #0:
        begin
          lex:=koniec; ok:=true;
        end;
      '{':
        begin
          repeat pis; znak; until z in ['}',#0];
          pis; znak;
        end;
      '/':
        begin
          pis; znak;
          if z='/' then begin
            repeat pis; znak; until z in [#1,#0];
          end;
        end;
      '''':
        begin
          repeat pis; znak; until z in ['''',#0,#1];
          pis; znak;
        end;
      'A'..'Z','a'..'z','_':
        begin
          hodn:=''; lex:=ident; ok:=true;
          while z in ['A'..'Z','a'..'z','0'..'9','_'] do begin
            hodn:=hodn+z; znak;
          end;
        end;
      else
        pis; znak;
    end;
  until ok;
end;
  • samotný program, načíta z dvoch komponentov editovací riadok dva reťazce - identifikátor, ktorý nahrádzame a reťazec, ktorým nahrádzame (Edit1 a Edit2); v textovej ploche Memo1 sa vypíše obsah súboru pred aj po nahrádzaní

program nahradí identifikátor iným reťazcom:

procedure TForm1.FormCreate(Sender: TObject);
begin
  Memo1.Lines.LoadFromFile('unit2.pas');
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  r1,r2:string;      // všetky výskyty r1 zmení na r2
begin
  r1:=LowerCase(Trim(Edit1.Text));
  r2:=Trim(Edit2.Text);
  if (r1='') or (r2='') then begin
    ShowMessage('Najprv zadaj oba reťazce');
    exit;
  end;
  AssignFile(t,'unit2.pas'); Reset(t);
  AssignFile(t1,'unit2a.pas'); Rewrite(t1);
  znak; skener;
  while lex<>koniec do begin
    if LowerCase(hodn)=r1 then write(t1,r2)
    else write(t1,hodn);
    skener;
  end;
  CloseFile(t); CloseFile(t1);
  DeleteFile('unit2.pas');
  RenameFile('unit2a.pas','unit2.pas');
  Memo1.Lines.LoadFromFile('unit2.pas');
end;
  • použili sme dve systémové procedúry DeleteFile a RenameFile - aby sme mohli prepísať pôvodný obsah súboru unit2.pas novým opraveným obsahom

Príklad

Program bude čítať textový súbor unit3.pas, v ktorom sa nachádza nejaký pascalovský program a vytvorí súbor unit3a.pas, ktorý bude maximálne zhustený na nejakú zadanú šírku riadka (napr. 80 znakov). V každom riadku bude maximálny počet znakov, ktoré sa sem zmestia a v súbore bude minimálny počet medzier tak, aby program ešte ostal funkčný. Komentáre z programu vynecháme.

  • aby bol program rovnako funkčný ako originál, musíme komentáre, ktoré začínajú znakom $, tiež preniesť do výstupu - bude to pre nás špeciálny symbol, napr. {$R *.dfm}
  • niektoré pascalovské viacznakové lexémy by mohli robiť problémy, keby sa rozdelili do dvoch riadkov, napr. znak priradenia :=, relačné operátory <=, dve bodky pre interval .. a pod. - musíme z nich vyrobiť jedinú lexému typu symbol
  • všimnite si aj spracovanie apostrofu v znakových reťazcoch

najprv dosť vylepšený skener:

var
  lex: (koniec,ident,cislo,retazec,symbol);
  hodn: string;

procedure skener;
var
  ok:boolean;
begin
  repeat
    ok:=true;
    case z of
      #0: lex:=koniec;
      ' ',#1,#9:    // #9 znamená znak tabulátor
        begin
          ok:=false; znak;
        end;
      '{':
        begin
          hodn:=z; znak; lex:=symbol;
          ok:=z='$';             // napr. pre {$R *.dfm}
          while not (z in ['}',#0]) do begin
            hodn:=hodn+z; znak;
          end;
          hodn:=hodn+z; znak;
        end;
      '''':
        begin
          znak; hodn:=''; lex:=retazec;
          repeat
            while not (z in ['''',#0,#1]) do begin
              hodn:=hodn+z; znak;
            end;
            if z='''' then znak;
            if z='''' then hodn:=hodn+'''';
          until z<>'''';
        end;
      'A'..'Z','a'..'z','_':
        begin
          hodn:=''; lex:=ident;
          while z in ['A'..'Z','a'..'z','0'..'9','_'] do begin
            hodn:=hodn+z; znak;
          end;
        end;
      '#','0'..'9':
        begin
          hodn:=z; znak; lex:=cislo;
          while z in ['0'..'9'] do begin
            hodn:=hodn+z; znak;
          end;
        end;
      '/':
        begin
          znak;
          if z='/' then begin     // komentár
            repeat znak until z in [#1,#0];
            ok:=false;
          end
          else begin
            hodn:='/'; lex:=symbol;
          end;
        end;
      ':':
        begin
          hodn:=z; znak; lex:=symbol;
          if z='=' then
            begin hodn:=hodn+z; znak; end;
        end;
      '.':
        begin
          hodn:=z; znak; lex:=symbol;
          if z='.' then
            begin hodn:=hodn+z; znak; end;
        end;
      '<','>':
        begin
          hodn:=z; znak; lex:=symbol;
          if (z='=') or (hodn='<') and (z='>') then
            begin hodn:=hodn+z; znak; end;
        end;
      else
        lex:=symbol; hodn:=z; znak;
    end;
  until ok;
end;
  • samotný algoritmus zhusťovania textu - medzi niektoré dvojice za sebou idúcich lexém musíme pridávať medzeru

vytváranie zhusteného pascalovského programu:

procedure TForm1.Button1Click(Sender: TObject);
const
  maxsir = 80;
var
  t1:TextFile;
  i,sir:integer;   // sir - doterajšia šírka riadka
  b0,b1:boolean;   // či vkladať medzery medzi lexémy
  s:string;
begin
  AssignFile(t,'unit3.pas'); Reset(t);
  AssignFile(t1,'unit3a.pas'); Rewrite(t1);
  sir:=0; b0:=false;
  znak; skener;
  while lex<>koniec do begin
    b1:=lex in [cislo,ident]; s:=hodn;
    if lex=retazec then begin
      s:='';
      for i:=1 to length(s) do
        if hodn[i]='''' then s:=s+''''''
        else s:=s+hodn[i];
      s:=''''+hodn+'''';
    end;
    if b0 and b1 then s:=' '+s;
    b0:=b1;
    if (sir>0) and (sir+length(s)>maxsir) then begin
      writeln(t1); sir:=0;
    end;
    if (sir=0) and (s[1]=' ') then delete(s,1,1);
    write(t1,s); inc(sir,length(s));
    skener;
  end;
  CloseFile(t); CloseFile(t1);
end;

NDÚ:

  • dorobte do skenera aj (*...*) komentáre
  • dorobte skener aj pre reálne čísla, šestnástkové konštanty '$' a znakové konštanty s '#'
  • zameňte všetky identifikátory premenných (nie rezervovaných slov pascalu) identifikátormi, ktoré obsahujú len znaky O, 0 a Q (písmeno O, nula a Q), napr. OOOO, OOO0, OO0O, OO00, O0OO a pod. - aby sa program stal maximálne nečitateľný, ale aby ostal funkčný

Bitmapy

v tejto časti môžete použiť súbory z predchádzajúcich prednášok obrazky.zip ale aj nové súbory s obrázkami obrazky2.zip

  • zatiaľ sme poznali jeden spôsob, ako vložiť obrázok (súbor .BMP) do grafickej plochy: pomocou LoadFromFile

prečítanie obrázka zo súboru do grafickej plochy Image:

procedure TForm1.Button1Click(Sender: TObject);
begin
  Image1.Picture.LoadFromFile('tiger.bmp');
end;
  • pomocou tohto spôsobu sa niekedy zmení grafická plocha a tiež bude nepoužiteľný, keď potrebujeme bitmapu umiestniť nie do ľavého horného rohu, ale na nejaké konkrétne súradnice
  • budeme pracovať s novým objektom - bitmapa - môžeme si všimnúť istú podobnosť s objektom korytnačka:
    • deklarovaním premennej bmp:TBitmap objekt (inštancia) ešte nevzniká - nový objekt treba ešte vytvoriť
    • bmp:=TBitmap.Create - vytvorí sa nová bitmapa (obrázok), ktorý je zatiaľ prázdny - má rozmery 0x0
    • do existujúceho objektu bitmapa môžeme prečítať obrázok zo súboru pomocou LoadFromFile (ak už bitmapa mala nejaký obsah, tento sa stráca a nahradí sa prečítaným obrázkom - zmení sa jej veľkosť)
    • teraz môžeme "opečiatkovať" náš nový objekt bitmapa do grafickej plochy Image1 pomocou metódy Draw
    • a mali by sme si na záver zvyknúť na jedno dôležité pravidlo: vždy, keď skončíme pracovať s objektom bitmapa, tak ju musíme uvoľniť zo systému pomocou metódy Free - bitmapa je totiž taký špeciálny objekt, ktorý odčerpáva zdroje z Windows a ak takýchto zdrojov Windows minieme priveľa, systém sa môže zrútiť...

bitmapa do pozadia grafickej plochy:

procedure TForm1.Button1Click(Sender: TObject);
var
  bmp:TBitmap;
begin
  bmp:=TBitmap.Create;
  bmp.LoadFromFile('tiger.bmp');
  Image1.Canvas.Draw(0,0,bmp);
  bmp.Free;
end;
  • metóda Draw opečiatkuje bitmapu do Canvasu na súradnice zadané dvoma prvými parametrami - tieto určujú ľavý horný roh kladeného obrázka - pritom môže nejaká časť obrázka z plochy vypadnúť; niekedy sa môžu hodiť aj záporné súradnice obrázka ...
  • existuje ešte špeciálny prípad metódy Draw - položenie obrázka do plochy so zmenou veľkosti StretchDraw - obrázok môžeme ľubovoľne zmenšiť alebo zväčšiť, môžeme ho napríklad natiahnuť na celú grafickú plochu
  • StretchDraw má dva parametre: prvým je obdĺžnik (rectangle), do ktorého treba umiestniť obrázok a druhým je samotná bitmapa; na definovanie obdĺžnika často použijeme konštrukciu Rect(x1,y1,x2,y2), v ktorej zadáme súradnice ľavého horného a pravého dolného rohu oblasti, napr.

natiahnutie obrázka na celú plochu:

procedure TForm1.Button2Click(Sender: TObject);
var
  bmp:TBitmap;
begin
  bmp:=TBitmap.Create;
  bmp.LoadFromFile('tiger.bmp');
  Image1.Canvas.StretchDraw(Rect(0,0,Image1.Width,Image1.Height),bmp);
  bmp.Free;
end;
  • ukážeme typickú prácu s bitmapou - opečiatkujeme obrázok viackrát vedľa seba a pod seba tak, aby presne vyplnil celé pozadie grafickej plochy

okachličkovanie plochy:

procedure TForm1.Button3Click(Sender: TObject);
var
  bmp:TBitmap;
  x,y,w,h:integer;
begin
  bmp:=TBitmap.Create;
  bmp.LoadFromFile('pozadie.bmp');
  w:=Image1.Width; h:=Image1.Height; y:=0;
  while y<h do begin
    x:=0;
    while x<w do begin
      Image1.Canvas.Draw(x,y,bmp);
      inc(x,bmp.Width);
    end;
    inc(y,bmp.Height);
  end;
  bmp.Free;
end;

NDÚ

  • vycentrujete bitmapu do grafickej plochy
  • vykachličkujte plochu dvojnásobne zväčšeným (zmenšeným) obrázkom
  • natiahnite obrázok tak, aby pokryl celú plochu, ale pritom, aby ostal pomer strán obrázka zachovaný - pravdepodobne obrázok v jednom rozmere môže vypadnúť z plochy
  • predpokladajme, že máme pripravených 8 súborov .BMP - bitmáp malých obrázkov bmp1.bmp, bmp2.bmp, ...; po zatlačení tlačidla sa na náhodnom mieste plochy položí náhodne vybraný jeden z obrázkov

náhodný obrázok na náhodnú pozíciu plochy:

procedure TForm1.Button4Click(Sender: TObject);
var
  bmp:TBitmap;
begin
  bmp:=TBitmap.Create;
  bmp.LoadFromFile('bmp'+IntToStr(random(8)+1)+'.bmp');
  Image1.Canvas.Draw(random(Image1.Width-bmp.Width),
                     random(Image1.Height-bmp.Height),bmp);
  bmp.Free;
end;
  •  ak použije obrázky zo súboru obrazky2.zip, môžete si všimnúť, že niektoré časti obrázkov sú biele, ale bolo by krajšie, keby boli priesvitné - bitmapám môžeme jednu farbu určiť ako priesvitnú a potom kladenie obrázka do plochy pomocou Draw alebo StretchDraw ponechá priesvitné časti nezmenené - bitmape musíme zadefinovať dve stavové premenné (vlastnosti) TransparentColor a Transparent - prvou definujeme farbu, ktorá sa stane priesvitnou a druhou zapíname (môžeme aj vypnúť) režim priesvitnej farby, napr.

obrázky s priesvitnými časťami:

procedure TForm1.Button5Click(Sender: TObject);
var
  bmp:TBitmap;
begin
  bmp:=TBitmap.Create;
  bmp.LoadFromFile('bmp'+IntToStr(random(8)+1)+'.bmp');
  bmp.TransparentColor:=clWhite;
  bmp.Transparent:=true;
  Image1.Canvas.Draw(random(Image1.Width-bmp.Width),
                     random(Image1.Height-bmp.Height),bmp);
  bmp.Free;
end;
  • môžeme pracovať nielen s bitmapami, ktoré sme prečítali zo súboru, ale bitmapu si môžeme nakresliť aj sami: bitmapa má rovnaký Canvas ako grafická plocha Image1 a teda do nej môžeme kresliť rovnakým spôsobom
  • ak nebudeme do práve vytvorenej bitmapy (TBitmap.Create) čítať súbor pomocou LoadFromFile, ale chystáme sa kresliť do jej Canvasu, musíme jej najprv určiť veľkosť (bmp.Width a bmp.Height) a tiež Canvas najprv vyfarbiť nejakou farbou (napr. pomocou FillRect)

programom vytvorený obrázok:

procedure TForm1.Button6Click(Sender: TObject);
var
  bmp:TBitmap;
  i:integer;
begin
  bmp:=TBitmap.Create;
  bmp.Width:=100; bmp.Height:=100;
  bmp.Canvas.Brush.Color:=clWhite;
  bmp.Canvas.FillRect(Rect(0,0,bmp.Width,bmp.Height));
  for i:=10 downto 1 do
    with bmp.Canvas do begin
      if odd(i) then Brush.Color:=clYellow
      else Brush.Color:=clRed;
      Ellipse(50-5*i,50-5*i,50+5*i,50+5*i);
    end;
  bmp.TransparentColor:=clWhite;
  bmp.Transparent:=true;
  Image1.Canvas.Draw(random(Image1.Width-100),random(Image1.Height-100),bmp);
  bmp.Free;
end;
  • do bitmapy môžeme opečiatkovať aj iné bitmapy - vytvoríme bitmapu so žltým kruhom a do nej opečiatkujeme náhodne vybranú bitmapu, napr.

použitie dvoch bitmáp:

procedure TForm1.Button7Click(Sender: TObject);
var
  bmp,bmp1:TBitmap;
begin
  bmp:=TBitmap.Create;
  bmp.Width:=100; bmp.Height:=100;
  bmp.Canvas.Brush.Color:=clWhite;
  bmp.Canvas.FillRect(Rect(0,0,bmp.Width,bmp.Height));
  with bmp.Canvas do begin
    Brush.Color:=clYellow;
    Ellipse(0,0,100,100);
  end;
  bmp.TransparentColor:=clWhite;
  bmp.Transparent:=true;

  bmp1:=TBitmap.Create;
  bmp1.LoadFromFile('bmp'+IntToStr(random(5)+1)+'.bmp');
  bmp1.TransparentColor:=clWhite;
  bmp1.Transparent:=true;
  bmp.Canvas.Draw((100-bmp1.Width) div 2,(100-bmp1.Height) div 2,bmp1);
  bmp1.Free;

  Image1.Canvas.Draw(random(Image1.Width-100),random(Image1.Height-100),bmp);
  bmp.Free;
end;
  • je ešte veľa iných možností, ako pracovať s bitmapami, napr.
    • namiesto bmp.LoadFromFile('bmp.bmp'); môžeme pomocou bmp.Assign(Image1.Picture); načítať-prekopírovať obsah celej grafickej plochy do bitmapy
    • z grafickej plochy môžeme do bitmapy prekopírovať len časť, napr. bmp.Canvas.Draw(-200,-100,Image1.Picture.Graphic); opečiatkuje celú grafickú plochu do Canvasu bitmapy - tá časť, ktorá sa nezmestí, bude odrezaná - zrejme bitmapa už musí mať pred týmto volaním nastavenú veľkosť, napr. bmp.Width:=100; bmp.Height:=100; - podobne by sme mohli použiť aj StretchDraw
    • metóda CopyRect je podobná StretchDraw, len môžeme zadať nielen obdĺžnik kam sa kopíruje, ale aj obdĺžnik (výrez), ktorý sa bude kopírovať; CopyRect má tri parametre:
      • obdĺžnik kam sa kopíruje (rovnako ako pre StretchDraw)
      • Canvas obrázka/plochy, ktorý sa kopíruje (napr. bmp.Canvas, Image1.Canvas, ...)
      • obdĺžnik výrezu obrázka, ktorý sa bude kopírovať, napr. Rect(100,100,200,150);
  • použitie CopyRect na jednoduchom príklade: z bitmapy tigra "vystrihneme" len hlavu

opečiatkovanie len časti obrázka:

procedure TForm1.Button8Click(Sender: TObject);
var
  bmp:TBitmap;
  x1,y1,x2,y2,w,h:integer;
begin
  bmp:=TBitmap.Create;
  bmp.LoadFromFile('tiger.bmp');
  x1:=random(Image1.Width); y1:=random(Image1.Height); // kam

  x2:=64; y2:=51;   // odkiaľ
  w:=130; h:=152;   // veľkosť toho, čo vystrihnem

  Image1.Canvas.CopyRect(Rect(x1,y1,x1+w,y1+h),bmp.Canvas,Rect(x2,y2,x2+w,y2+h));
  bmp.Free;
end;
  • ak vám pri práci s grafickou plochou (nielen pri práci s bitmapami) bliká celá plocha, môžeme formuláru nastaviť takúto stavovú premennú (môže kvôli tomu sa mierne spomaliť zobrazovanie v Image)

aby grafická plocha neblikala:

procedure TForm1.FormCreate(Sender: TObject);
begin
  DoubleBuffered:=true;
end;

Bitmapa ako globálna premenná

  • často sa nám môže hodiť, aby sme nemuseli často používanú bitmapu (ale aj viac bitmáp - možno aj pole bitmáp) tesne pred použitím vytvoriť a prečítať zo súboru a po použití (napr. po Draw) ju pomocou Free uvoľniť
  • najjednoduchším spôsobom je vytvorenie globálnych premenných pri štarte formulára - FormCreate
  • potom je dobre nezabudnúť uvoľniť takéto bitmapy pri konci aplikácie - FormDestroy

bitmapa ako globálna premenná:

var
  bmp:TBitmap;

procedure TForm1.FormCreate(Sender: TObject);
begin
  bmp:=TBitmap.Create;
  bmp.LoadFromFile('tiger.bmp');
  DoubleBuffered:=true;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  x,y,w,h:integer;
begin
  w:=50+random(200); h:=50+random(200);
  x:=random(Image1.Width-w); y:=random(Image1.Height-h);
  Image1.Canvas.StretchDraw(Rect(x,y,x+w,y+h),bmp);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  bmp.Free;
end;

Kópia bitmapy

  • ak potrebujeme prekopírovať obsah jednej bitmapy do druhej, nikdy nesmieme použiť "obyčajné" priradenie, napr.

!!! chybné riešenie !!!

var
  b1,b2:TBitmap;
begin
  b1:=TBitmap.Create; b2:=TBitmap.Create;
  b1.LoadFromFile('miri.bmp');

  b2:=b1;    // tá istá bitmapa dostala druhé meno - b2
  // ... požívanie bitmapy b2 ...
  b1.Free; b2.Free;
  • táto časť programu spadne, lebo po b1.Free už neexistuje ani b2 a teda sa nedá urobiť b2.Free ...
  • prvé správne riešenie

kopírovanie jednej bitmapy do Canvasu druhej bitmapy:

var
  b1,b2:TBitmap;
begin
  b1:=TBitmap.Create; b2:=TBitmap.Create;
  b1.LoadFromFile('miri.bmp');

  b2.Width:=b1.Width; b2.Height:=b1.Height;
  b2.Canvas.Draw(0,0,b1);
  // ... požívanie bitmapy b2 ...
  b1.Free; b2.Free;
  • druhé správne riešenie

použitie metódy Assign:

var
  b1,b2:TBitmap;
begin
  b1:=TBitmap.Create; b2:=TBitmap.Create;
  b1.LoadFromFile('miri.bmp');

  b2.Assign(b1);
  // ... požívanie bitmapy b2 ...
  b1.Free; b2.Free;

 Zhrnutie triedy TBitmap

TBitmap je preddefinovaná trieda, ktorá slúži na manipuláciu s obrázkami - môžeme si ju predstaviť ako obsah súboru s príponou .BMP (dá sa s ním pracovať napr. v programe Paint/Skicár). Táto trieda má niekoľko užitočných stavových premenných (vlastnosti - property) a metód.

niektoré stavové premenné:

  • Width, Height - momentálna šírka a výška obrázka (môžeme ju zmeniť priradením nových hodnôt do týchto premenných)
  • Canvas - grafická plocha obrázka
  • Transparent - či má nejaké priesvitné časti (inak je to nepriesvitný obdĺžnik)
  • TransparentColor - ktorá farba v obrázku je považovaná za priesvitnú

niektoré metódy:

  • konštruktor Create - vytvorí zatiaľ prázdny obrázok
  • Free - uvoľní bitmapu z pamäti Windows
  • LoadFromFile - načíta obrázok zo súboru vo formáte .BMP
  • SaveToFile - uloží obrázok do súboru vo formáte .BMP
  • Assign - urobí kópiu obrázka z inej bitmapy

Môžeme pracovať s Canvasom bitmapy, t.j. s dvojrozmerným poľom farebných Pixelov (bodov) - do bitmapy môžeme kresliť, môžeme pracovať s jednotlivými pixelmi - úplne rovnako ako v "obyčajnej" grafickej ploche (TImage)

  • Canvas.FillRect
  • Canvas.Pixels[riadok,stĺpec]
  • Canvas.Rectangle, Ellipse, TextOut, ...
  • Canvas.MoveTo, LineTo, Polygon, Polyline, FloodFill, ...

a teda môžeme týmto metódam nastaviť pero, štetec, font, ..., napr. Canvas.Pen.Color:=...

  • Canvas.Draw - "opečiatkuje" inú bitmapu
  • Canvas.StretchDraw - "opečiatkuje" inú bitmapu, pričom ju môže zväčšiť/zmenšiť
  • Canvas.CopyRect - "opečiatkuje" výrez iného Canvasu (bitmapy alebo grafickej plochy)
  • Canvas.BrushCopy - podobný CopyRect - jednu z farieb pri kopírovaní vie nahradiť inou


© 2002 AB, KVI
blaho@fmph.uniba.sk