Bitmaps Roteren

Download het rotatie exerciser programma
bekijk de source code
Download het complete Delphi-7 project


Introductie

Dit artikel beschrijft methodes voor bitmap rotatie.
Het hele Delphi project bestaat uit 3 units:
    - unit1: exerciser om de rotaties te testen
    - rotation_unit : procedures voor de rotaties
    - clock_unit : tijdmeting in microseconden

Exerciser

Het form heeft knoppen om bitmaps te laden of te bewaren.
Er kunnen demo plaatjes worden geselecteerd.
Ook selecteerbaar zijn 3 manieren van roteren:
    - coarse: snel, maar minder nauwkeurig
    - medium: 50% langzamer maar de destination bitmap wordt geheel gedekt
    - fine: 9 maal zo traag als medium, maar met soft edges
De destination bitmap wordt getoond in paintbox1.
Bij het bewegen van de muispointer (linkermuisknop ingedrukt) over het scherm roteert
de source bitap.

Hieronder wordt een voorbeeld getoond van medium rotation:
Rotatie vindt plaats van de source- naar de destination bitmap.
In coarse mode wordt de source bitamap pixel voor pixel gescand en op de destination map geprojecteerd.
Daarom is het mogelijk, dat niet elk pixel van de destination map wordt beschreven.
Er kunnen in het geroteerde beeld pixels met de achtergrondkleur opduiken.
In medium mode worden de pixels van de destination map gescand en hun waarde geladen van de source map.
Dit zorgt ervoor, dat elk pixel van de destination map wordt herschreven.
In fine mode is het scannen gelijk aan de medium mode, maar extra is dat elk pixel is verdeeld in 9 subpixels.
Subpixels kunnen op verschillende sourcepixels vallen.
De kleuren van de sourcepixels worden gesommeerd en door 9 gedeeld.
Dat levert de uiteindelijke kleur van een destination pixel.

Programmeren

De programmeur moet de source en de destination bitmap creëren.
Voordat een rotatie kan plaatsvinden moet de the rotation_unit geïnformeerd worden over de naam van de bitmap.
Dat gebeurt met een call naar
 procedure setmaps(sourcemap,destination map)
 
Setmaps zet het pixelformat van beide bitmaps op 32 bit.
Ook worden de afmetingen van de destination bitmap ingesteld om alle mogelijke rotaties te kunnen bevatten.

Hierna kan in de source map worden getekend, waarna
 procedure coarserotate(deg : word)
 procedure mediumrotate(deg: word)
 procedure finerotate(deg: word)
 
zorgen voor een geroteerde afbeelding in de destination map.
Deg is de rotatiehoek in graden (0 .. 360), rechtsom gerekend.
Rotatie is met de klok mee, dus een 90 graden rotatie linksom moet worden opgegeven met 270 graden.

Vergeet niet om de setmaps procedure aan te roepen na laden van de source map uit een file.
Dat zorgt voor het juiste 32 bit format en afmetingen van de destination map.

Theorie

Medium mode

De theorie wordt uitgelegd in een x,y coördiatenstelsel.
Onderstaande afbeelding toont het coördinatenstelsel ten opzichte van de bitmap:
    source map destination map
De destination map is altijd een vierkant.
Ook zijn breedte en hoogte altijd oneven.
Maps en coördinatenstelsel zijn verdeeld in 4 kwadranten:
    1 : rechts onder ( x+ , y+)
    2 : links onder (x- , y+)
    3 : links boven (x- , y-)
    4 : rechts boven (x+ , y-)
Pixel scanning van de destination map is horizontaal vanuit het midden.
Bij kwadranten 1 en 4 wordt een row gescand van links naar rechts.
Bij kwadranten 2 en 3 is dat van rechts naar links.

Voor een bepaalde positie (x,y) van het coördinatenstelsel moet de positie van het originele pixel
worden berekend.
Die posities leveren weer het bijbehorende pixel in elke map.

We bekijken een rotatie over 30 graden met de bovenstaande bitmaps
    scanning the maps, quadrant 1
De source map is rood getekend.
Gescand worden de zwart getekende pixels van de destination map en de kleur wordt gekopieerd van het
bijbehorende rode pixel van de source map.

Berekeningen

Nu beschrijven we hoe de positie op de source map wordt berekend gegeven positie (x,y) van de destination map.
(x,y) wordt beschouwd als de optelling van vectoren (x,0) en (0,y), dus de x- en y- vectoren.
Deze vectoren worden afzonderlijk geroteerd.
in bovenstaande figuur is (x',y') de positie op de sourcemap die hoort bij (x,y) van de destination map.

Rotatie van x levert vector OD.
Rotatie van y levert OC.
OD is de optelling van (horizontale) vector OB en (vertikale) vector BD.
OC is de optelling van (horizontale) vector OA en (vertikale) vector AC.

Optelling van de horizontale vectoren maakt x', optelling van de vertikale vectoren maakt y'.

sin( ) en cos( ) functies hebben een hoek in radialen nodig.
De aanroep van de rotatie procedure is in graden.
Constante deg2rad = p / 180 zet graden om in radialen:
    radians := deg2rad * degree
Voor een rotatiehoek a radialen en (x,y) als coördinaten :
  • vsin := sin(a)
  • vcos := cos(a)
  • xtx = OB = x * vcos
  • xty = BD = x * vsin
  • ytx = OA = y * vsin
  • yty = AC = y * vcos
xty betekent: x to y, de bijdrage van de geroteerde x vector aan de uiteindelijke y vector.

Nu geldt voor kwadrant 1:
  • tx = xtx + ytx
  • ty = - xty + yty
Bij de andere kwadranten kunnen de tekens van xtx, ytx, xty of yty wisselen.

Addresseren van de pixels

We kunnen natuurlijk de bitmap property pixels[x,y] gebruiken,maar die is erg traag.
Ook de scanline[y] property, die de memory pointer naar het begin van een row levert, is erg traag.
Veel sneller werken pointers direct naar de pixels.

Omdat de bitmaps het 32 bit format hebben definiëren we
  type PDW = ^dword;
PDW is nu een pointer naar een 32 bit unsigned integer.

Adresberekeningen doen we met variablen in dword ( = cardinal) format.
Eerst moet de pointer naar [0,0] worden verkregen.
Daarvoor gebruikt setmaps de scanline[0] property.
scanline[1] levert verder de pointer naar pixel [0,1].
scanline[1] - scanline[0] geeft het verschil in pointers tussen opvolgende rijen.

(opmerking: de row 1 pointer is kleiner dan de row 0 pointer).

In de rotatieprocedure
  • PSBM = scanline[0] for the source map
  • PDBM = scanline[0] for the destination map
  • Slinestep = scanline[0] - scanline[1] for the source map
  • Dlinestep = scanline[0] - scanline[1] for the destination map
Ook moet een aanpassing worden gemaakt om de het centrum
van een bitmap op (0,0) van het coördinatenstelsel te leggen.
  • scx is het centrum x pixel van de source map
  • scy is he centrum y pixel van de source map
  • dcxy is het x en y centrum van de destination map
pixel (x,y) van de source wordt geadresseerd met
    trunctx := trunc(tx);   //tx is floating point format
    truncty := trunc(ty);   //...
    ttx := scx + trunctx;   //add center
    tty := scy + truncty;
    PS := PSBM - tty*Slinestep + (ttx shl 2);   //pointer to source pixel
    pix := PDW(PS)^;
pix krijgt de waarde van het source map pixel.

Voor kwadrant 1 is de pointer van de destination map
    Ybase1 := PDBM - (dcxy + y)*Dlinestep;
    PD := Ybase1 + ((dcxy+x) shl 2);
    PDW(PD)^ := pix;

Fine Rotation

Dit is een aanvulling op mediumrotation.
Pixel scanning is in beide gevallen gelijk, maar een pixel is nu opgedeeld in 9 subpixels:
Bij de start van procedure fineRotate wordt de offset tabel gemaakt.
Deze tabel is specifiek voor een bepaalde rotatiehoek en bevat per kwadrant de x,y offsets van elk subpixel.
Subpixels kunnen op de sourcemap in verschillende (aangrenzende) pixels vallen.
Voor elk subpixel worden nu de RGB kleuren van de sourcepixels opgeteld.
De uiteindelijke kleur ontstaat door te delen door 9.
Deze methode is dus ruwweg negen maal trager dan de medium rotatiemethode.
Maar de kleuren lopen nu vloeiender over, er ontstaan soft-edges.

Voor verdere details verwijs ik naar de source-code listing.
    mediumfine

Donaties

Het bitmap-rotatie-project is freeware.
Programmeren kost tijd en inspanning.
Bij commercieel gebruik van mijn rotatie procedures vraag ik daarom een kleine donatie voor mij website.
Stuur even een e-mail berichtje voor de details.