download program download Delphi (7) project Inleiding De Delphi TBitmap class bevat een twee dimensionaal array van bytes,words of cardinalsgenaamd canvas dat een afbeelding kan weergeven. Een individueel byte,word,cardinal van dit canvas heet pixel. Soms moet een afbeelding worden vergroot, bijvoorbeeld om gelijke grootte te handhaven bij hogere resoluties zoals bij een printer canvas. Dat kan geschieden door de bitmap te kopiëren naar een andere van verschillende grootte, gebruik makend van een geschikt algoritme. Een manier is om de pixels van de nieuwe bitmap één voor één te projecteren op de pixels van de originele bitmap en de gemiddelde waarde van dat gebied te berekenen. Ik noem dat de "projectie methode". Kijk hier voor een beschrijving van deze methode. Dat werkt prima in het geval van verkleining. Maar bij vergroting met een factor 2 of 3 is het resultaat niet vloeiend. In dit artikel beschrijf ik een andere methode die geschikter is:
- gebruik interpolatie om de lege pixels van de bestemmings bitmap op te vullen De volgende plaatjes laten de resultaten zien bij 3 voudige vergroting:
Met een vergrootglas is het verschil nog duidelijker te zien. TBitmap class In dit Delphi-7 project zijn de pixels 32 bit positieve integers (pf32bit).Dit is het interne format van zo'n pixel: Per kleur zijn er 8 bits voor de intensiteit, die dus kan varieren van 0..255. Bits 24..31 worden niet gebruikt. Het volgende plaatje laat de coördinaten van de pixels op een canvas zien. De bitmap scanline[y] property verschaft een pointer naar het eerste pixel van rij y. Een pixel kan gelezen of geschreven worden met .canvas.pixels[x,y] Dit is een relatief traag proces dat alleen geschikt is als het om weinig pixels gaat. Veel (50x) sneller is het adresseren van pixels met pointers naar hun geheugenplaats. Voor makkelijke berekening met deze pointers sla ik ze op als dwords (of cardinal). Hierna maken we een bitmap genaamd map met 100 rijen en 200 kolomman: type PDW = ^dword; ... var map : TBitmap; p0 : dword;//pointer to [0,0] pstep : dword;//pointer distance between rows .... begin map := TBitmap.create; with map do begin width := 200; height := 100; pixelformat := pf32bit; end; p0 := dword(map.scanline[0]); pstep := p0 - dword(map.scanline[1]);Nu kan de opdracht code color1 := map.canvas.pixels[12,75) //----1vervangen worden door color1 := PWD(p0 - 75*pstep + (12 shl 2))^; //------2Opmerking: In gevallen -1- en -2- hiervoor zijn de waarden van variabele color1 iets verschillend. In geval -1- staat het rode veld in bits 0..7 en blauw in bits 16..23. Dit is het Windows format voor 32 en 24 bit kleuren. Bij beschouwing van een 100*200 pixels canvas als één dimensinaal array[0..19999] van dwords, is het eerste dword [0] pixel positie [0,199], de links onder hoek. Dword [1] ligt op plaats [1,199] dat is 4 bytes verderop. Gaande van [0,199] naar [0,198] vraagt optelling van 4*200 = 800 bij de pointer. Pointers zijn byte adressen. De expressie (12 shl 2) is een snelle manier om 12 met 4 te vermenigvuldigen. De volgende rij in een kolom bereiken we door van de pointer 4 keer de waarde "aantal kolommen" af te trekken. Opmerking: Een bitmap is ook als een ééndimensionaal array A[ ] te beschouwen, waarbij bitmap.scanline[bitmap.height-1] naar A[0] wijst. Vergroten x 2 Een 5*5 bitmap vergroten we naar 10*10.De pixels die direct overgezet worden naar de nieuwe bitmap zijn met een getal aangegeven. Daarna worden de tussenliggende pixels A,B,C have berekend. De onderste rij en ook de meest rechtse kolom hebben een aparte aanpak nodig. Het project Voor details verwijs ik naar de source code. Er zijn twee units, -1- voor controle en afhandeling van events. De expansion_unit bevat de procedures voor de bitmap vergroting. Het begint met laden van een afbeelding in bitmap map1. .bmp en .jpg formaten zijn mogelijk. map1 wordt afgebeeld in paintbox1. bitmap map2 wordt gemaakt met 2x de breedte en hoogte van map1. procedure X2copy(map2,map1) produceert daarna de vergroting. procedure X2copy type TAIP = arry[1..8] of dword; var c1,c2,c3,c4 : dword; //colors of source map AIP : TAIP; //interpolation pixels pd0 : dword; //destination row 0 pointer pd1,pd2 : dword; //destination row pointers pdstep : dword; //destination row difference ps0 : dword; //source row 0 pointer psstep : dword; //source row difference x,y : word; //source pixel addressing py,py1 : dword; //scratch pointers De pixels worden verwerkt van links boven naar rechts onder, zoals we een boek lezen. variabelen x,y adresseren het bron pixel c1 {c staat voor color}. C1 wordt naar map2 gekopieerd. Pixels A,B,C worden berekend in procedure interpolate24(AIP,c1,c2,c3,c4); A is AIP[1] B is AIP[2] C is AIP[3] Interpolate24...{2x vergroting, 4 variabelen} roept aan procedure unpackColor(var r,g,b : byte; col : dword); begin b := col and $ff; col := col shr 8; g := col and $ff; col := col shr 8; r := col and $ff; end;voor het extraheren van de 8 bit r,g,b kleuren uit dword col. c1 heeft de r1,g1,b1 variabelen voor rood, groen, blauw. c2 heeft de r2,g2,b2 variabelen...etc. Dan worden de r,g,b waardes voor elk pixel A,B,C berekend. A = (C1+C2)/2 for r,g,b B = (C1+C3)/2 for r,g,b C + (C1+C2+C3+C4+3)/4 for r,g,b Tenslotte worden de r,g,b kleuren verpakt in AIP[1], AIP[2}..etc door aanroep van procedure PackColor(var col : dword; r,g,b : byte); begin col := ((r shl 16) or (g shl 8) or b); end;Zie de source code voor details. Rechter kolom Pixel c1 op [x,y] wordt direct gekopieerd. Pixel A is gelij aan c1. Pixels B en C zijn het gemiddelde van c1 en c2 wat berekend wordt door procedure interpolate22(var AIP : TAIP; c1,c2 : dword); //return AIP[1] var r,g,b,r1,g1,b1,r2,g2,b2 : byte; begin UnpackColor(r1,g1,b1,c1); UnpackColor(r2,g2,b2,c2); r := (r1 + r2) shr 1; g := (g1 + g2) shr 1; b := (b1 + b2) shr 1; packcolor(AIP[1],r,g,b); end;Onderste rij c1 wordt gekopieerd. B is gelijk aan c1. A en C zijn het gemiddelde van c1,c2. Vergroten met factor 3 Hieronder staat een 3x4 bitmap en de 3 voudige vergroting.De aanpak is hetzelfde als bij vergroting met factor 2 maar er is meer rekenwerk vereist. Eerst worden de 3x3 velden aangepakt. De interpolatie is wat lastiger. Ik bereken de pixels A,B,C,D,E,F,G,H als gewogen gemiddeldes van pixels C1,C2,C3,C4. Als een pixel (C1,C2..) afstand d heeft tot pixel (A,B,C...) dan is zijn weegfactor w = 1/d. De afstanden worden met de stelling van Pythagoras berekend. A = (1.C1 + 0.5C2)/(1+0.5) = 0.66C1 + 0.33C2 B = (0.5C2 + 1.C1)/(1+0.5) = 0.33C1 + 0.66C2 D = 0.36C1 + 0.23C2 + 0.23C3 + 0.18C4 zie het volgende plaatje: De berekening moet uiteraard voor elke kleur (r,g,b) van D worden gemaakt. Bovenste rij en linker kolom Deze pixels zijn een kopie van hun (onderliggende, rechtse) buren. Rechter kolom C1,C2 en interpolatie kleuren A,B worden worden ook naar de rechter kolom gekopieerd. Onderste rij C1 en C2 worden rechtstreeks gekopieerd. A,B zijn de interpolatie pixels. C1,C2,A,B worden gekopieerd naar de onderste rij van de niewe bitmap. Resultaten zichtbaar maken map1 (geladen van disk) wordt zichtbaar door kopiëren naar paintbox1.Paintbox1 heeft een vaste breedte en hoogte van 400*400 pixels. map2 is het resultaat van de vergroting en wordt getoond in paintbox2. Deze paintbox meet 800*800 pixels. Een horizontale- en vertikale scrollbar maakt mogelijk om grotere afbeeldingen te bekijken. De scrollbar max property moet per afbeelding worden aangepast. Dat doet de volgende code var d : smallInt; ... begin d := map2.width - paintbox2.Width; if d < 0 then d := 0; Hscrollbar.max := d; Hscrollbar.position := 0; d := map2.Height - paintbox2.Height; if d < 0 then d := 0; Vscrollbar.Max := d; Vscrollbar.position := 0; end;Een scrollbar onChange event zorgt voor berekening van de rectangle op map2 die naar paintbox2 gekopieerd word: procedure TForm1.VscrollbarChange(Sender: TObject); //V,H scrollbar changes //repaint paintbox2 var BW,BH : word;//paintbox width,height rs,rd : Trect; //source,destination rect begin BW := paintbox2.Width; BH := paintbox2.Height; with rs do begin left := Hscrollbar.position; top := Vscrollbar.position; right := left + BW; bottom := top + BH; end; with rd do begin left := 0; top := 0; right := BW; bottom := BH; end; paintbox2.Canvas.CopyRect(rd,map2.Canvas,rs); end; Als afsluiting toon ik een voorbeeld van drievoudige vergroting met zowel de projectie- als de interpolatie methode: origineel: projectie methode: interpolatie methode: Afgebeeld is het wapen van de provincie Zeeland. Een wat vrije vertaling van de tekst (Luctor Et Emergo) is : "pompen of verzuipen". |
||||||||||||||