I wrote a DLL function some time ago for rotating an image in DBC. I just never bothered writing it in BASIC because I assumed it would be way too slow, so after some talk on the DBC challenges I thought I'd convert the function over. It works ok.
The function is called rotate_img. Basically it takes 1 source image, uses another image to draw the rotation on, then uses a 3rd image as the final rotated image. This prevents the original image from being disturbed and prevents any Sprites from getting wacky as the image is being updated.
The math behind the rotation is pretty simple. Looping the x width within the y height of the target, the method calculates where on the source image a rotated pixel would come from and then copies that color to the current x,y position of the target. It works backwards in a sense. Since the source image is never disturbed, this ensures that the rotated image will not get distorted no matter how much rotation there is (sin() and cos() add up a lot of little errors over time).
The parameters are documented inside the function; but I should just mention what I refer to as matting. An image is rectanglular and when it is rotated, the spaces where the corners were have to be filled, and unless the image is resized, the corners get clipped as the image rotates. I have included 3 matting modes:
0 - maintain the original image's size. As the image is rotated, the corners will be clipped and the spaces where the corners were will be filled with black.
1 - Expanded matting. Black matting will be extended around the image increasing the overall image size but allows the image to rotate without clipping
2 - Shrink Image. The original image will be shrunk within black matting that is the actual size of the image. Rotation can occur without clipping.
Also, by default, the image rotates around it's center. You can change this using the offset parameters. Just using the offsets without changing the rotation, you can move the image around inside it's matting. I may expand the matting so that an offset image is fully visible and doesn't clip... that'll be later.
The function works for 16 bit or 32 bit images.
This example rotates a sprite. Right click to toggle transparency (of the matting) and press 0,1, or 2 to test the different matting types:
rem rotate image
rem by latch
rem 11/15/2008
set display mode 800,600,16
sync on
sync rate 0
rem potted flower image used as alternative for picture file formats
ink rgb(64,64,64),0
box 0,0,128,128
ink rgb(200,200,255),0
box 4,4,124,124
ink rgb(0,160,0),0
box 60,38,68,124
for xr=0 to 11
for yr=0 to 6
ellipse 77,72,xr,yr
ellipse 51,72,xr,yr
next yr
next xr
anginc#=360.0/6.0
ang#=0-anginc#
ink RGB(255,255,0),0
for f=1 to 6
inc ang#,anginc#
x=16*cos(ang#)+64
y=16*sin(ang#)+34
for r=0 to 10
circle x,y,r
circle x,y-1,r
next r
next f
ink rgb(10,10,10),0
for r=0 to 11
circle 64,34,r
circle 63,34,r
next r
ink RGB(128,0,0),0
box 48,96,80,124
get image 1,0,0,129,129,1
sync
white=rgb(255,255,255)
dim mat$(2)
mat$(0)="0 Normal Matting - Image size unaltered - clipping in effect"
mat$(1)="1 Expanded Matting - Border around image added - no clipping"
mat$(2)="2 Shrink Image - Image size reduced inside borders - no clipping"
do
cls 128
rem display info
ink white,0
text 0,0,"FPS : "+str$(screen fps())
text 0,20,"0, 1, or 2 to change Matting"
text 0,40,"RIGHT CLICK to toggle Transparency"
text 0,60,mat$(mat)
rem matting settings
if scancode() <> 0
if inkey$()="0" then mat=0
if inkey$()="1" then mat=1
if inkey$()="2" then mat=2
endif
rem set up mouse click toggles
orc=nrc
nrc=mouseclick() & 2
if nrc > orc then trp=1-trp
rem rotate the sprite/image
ang#=wrapvalue(ang#+1)
rotate_img(1,3,2,1,2,1,ang#,mat,0,0)
sprite 1,100,100,2
set sprite 1,0,trp
sync
loop
end
function rotate_img(imgin,imgdraw,imgout,mem1,mem2,bmp,angle#,matting,xoffset,yoffset)
remstart
rotate image
by latch
11/15/2008
imgin = the image to rotate - will be copied and not affected
imgdraw = the image to perform the drawing on so as not to affect
imgin or imgout
imgout = the number of the rotated image to create
mem1 = the source image memblock
mem2 = the target image memblock that is used to create imgout
bmp = the bitmap to draw on in the background
angle# = the angle to rotate the image (- clockwise / + counter)
matting = the background behind the rotated image.
0 is no matting. If the image is rotated beyond
imgin's original size, the corners will be clipped
1 is extra matting. The background of the created
image will be large enough to NOT clip the corners
if the image is rotated outside of imgin's original size
2 shrink image. Imgout will be small enough to fit
within imgin's original boundaries with no clipping
the xoffset and yoffset are used to change the rotation
center of the image. This can alos be used just to shift the
images position (will clip image areas out of bounds)
remend
make memblock from image mem1,imgin
wd1=memblock dword(mem1,0)
ht1=memblock dword(mem1,4)
dp=memblock dword(mem1,8)
bytes=dp/8
delete memblock mem1
rem if matting=1 create a larger image, if 2 use the values for later
rem now create a new black image that is the size of the diagonals
if matting >= 1
wd2=sqrt((wd1*wd1)+(ht1*ht1))
ht2=wd2
else
wd2=wd1
ht2=ht1
endif
create bitmap bmp,wd2+2,ht2+2
cls 0
rem center the old image onto the new black background
posx=(wd2-wd1)/2
posy=(ht2-ht1)/2
paste image imgin,posx,posy
get image imgdraw,0,0,wd2+1,ht2+1,1
sync
rem matting = 2 so shrink the matted image created for matting=1
if matting >=2
bmp2=bmp+1
while bitmap exist(bmp2)
inc bmp2
endwhile
create bitmap bmp2,wd1+1,ht1+1
copy bitmap bmp,0,0,wd2,ht2,bmp2,0,0,wd1,ht1
sync
endif
rem now make 2 memblocks. 1 for the original image, and one to copy to
if matting>=2
make memblock from bitmap mem1,bmp2
delete bitmap bmp2
else
make memblock from image mem1,imgdraw
endif
wd1=memblock dword(mem1,0)
ht1=memblock dword(mem1,4)
dp=memblock dword(mem1,8)
bytes=dp/8
make memblock mem2,12+((wd1*ht1)*bytes)
write memblock dword mem2,0,wd1
write memblock dword mem2,4,ht1
write memblock dword mem2,8,dp
rem rotate image one and copy the rotation information to memblock 2
cx=(wd1/2)+xoffset
cy=(ht1/2)+yoffset
if angle#=90 or angle#=270
cang#=0
else
cang#=cos(angle#)
endif
if angle#=180 or angle#=360
sang#=0
else
sang#=sin(angle#)
endif
for y=0 to ht1-1
for x=0 to wd1-1
rem target is the current x,y position of the image to draw as rotated
rem the source is the position from which to grab a color from
rem the original image. Because we never alter the original image,
rem errors will not accumulate in the rotation avoiding distorting
rem the image
targetx=x-cx
targety=y-cy
sourcex=((targetx*cang#)-(targety*sang#))+cx
sourcey=((targetx*sang#)+(targety*cang#))+cy
if sourcex < wd1 and sourcex >= 0 and sourcey < ht1 and sourcey >= 0
targetpixel=((x+(y*wd1))*bytes)+12
sourcepixel=((sourcex+(sourcey*wd1))*bytes)+12
copy memblock mem1,mem2,sourcepixel,targetpixel,bytes
else
if bytes=4
rem 32 bit
rem if the area is outside of the image make black
targetpixel=((x+(y*wd1))*bytes)+12
write memblock dword mem2,targetpixel,0
else
if bytes=2
rem 16 bit
rem if the area is outside of the image make black
targetpixel=((x+(y*wd1))*bytes)+12
write memblock word mem2,targetpixel,0
endif
endif
endif
next x
next y
make image from memblock imgout,mem2
delete memblock mem1
delete memblock mem2
delete bitmap bmp
endfunction
Enjoy your day.