How to capture a rotated image

BlitzMax Forums/BlitzMax Beginners Area/How to capture a rotated image

Grey Alien(Posted 2007) [#1]
Hiya, if I want to draw an image rotated, then capture the resulting image into another image ready for drawing later (i.e. pre-rotated), how would I do that please. Using pixmaps or something? (I'm not familiar with them).

I don't want to rotate on the fly because I need to clip the image in a non-rotated rectangle later on.

Thanks in advance for any help you can offer.


H&K(Posted 2007) [#2]
Function GrabImage( image:TImage,x,y,frame=0 )or
Function GrabPixmap:TPixmap( x,y,width,height ) if you need to save it.


Grey Alien(Posted 2007) [#3]
Will that capture alpha information from a 32-bit png drawn to the screen? I guess there's no way to do this without drawing it first (or using render to texture?)


Dreamora(Posted 2007) [#4]
Alpha information are lost when drawn.
The backbuffer is without alpha, just RGB

If you need alpha you will need to get that information manually from the pixmap by transforming it through a matrix or filter and get the information from there.


Grey Alien(Posted 2007) [#5]
Gah!


Perturbatio(Posted 2007) [#6]
Or maybe have a grey scale version of the image that just shows the mask, rotate it the same way then get the "alpha" info from it by getting the grey tones?


MGE(Posted 2007) [#7]
Probably best to just develop a rotation algo using read/write pixel to the pixmap and then saving it.


Grey Alien(Posted 2007) [#8]
JGOware: Yes you are probably right.


ImaginaryHuman(Posted 2007) [#9]
Here is code to rotate an image in software:

'Software rotation demo

Strict
SetGraphicsDriver(GLMax2DDriver())
Graphics 640,480,0

'Prepare pixmaps
Local BackgroundPixmap:TPixmap=LoadPixmap("Background.jpg")
If PixmapFormat(BackgroundPixmap)<>PF_RGBA8888 Then BackgroundPixmap=ConvertPixmap(BackgroundPixmap,PF_RGBA8888)
Local SourcePixmap:TPixmap=LoadPixmap("RotoZoom.jpg")
If PixmapFormat(SourcePixmap)<>PF_RGBA8888 Then SourcePixmap=ConvertPixmap(SourcePixmap,PF_RGBA8888)
Local DestPixmap:TPixmap=CreatePixmap(640,480,PF_RGBA8888,4)
Local SourcePixelsPerRow=PixmapPitch(SourcePixmap)/4
Local DestPixelsPerRow=PixmapPitch(DestPixmap)/4
Local OpWidth:Int=PixmapWidth(SourcePixmap) 'Width of operation
Local OpHeight:Int=PixmapHeight(SourcePixmap) 'Height of operation
Local OpWidth2:Int=OpWidth/2
Local OpHeight2:Int=OpHeight/2
Local XCenter:Int'=OpWidth/2
Local YCenter:Int'=OpHeight/2

'Dummy alpha values
For Local aa:Int=0 To OpHeight-1
	For Local bb:Int=0 To OpWidth-1
		WritePixel(SourcePixmap,bb,aa,(ReadPixel(SourcePixmap,bb,aa)&$00FFFFFF)|(Int(bb*(256.0/OpWidth)) Shl 24)) 'Make alpha to see effect if image doesn't have one
	Next
Next

'Set up variables
Local PixmapWin:TPixmap=CreateStaticPixmap(PixmapPixelPtr(DestPixmap,0,0),OpWidth-1,OpHeight-1,SourcePixelsPerRow*4,PF_RGBA8888)
Local Angle:Float=0
Local Magnification:Float=1.0
Local Y_UAdd:Float,Y_VAdd:Float,X_UAdd:Float,X_VAdd:Float
Local Y_U:Float,Y_V:Float,X_U:Float,X_V:Float
Local SourceBase:Int Ptr=Int Ptr(PixmapPixelPtr(SourcePixmap,0,0))
Local DestBase:Int Ptr
Local SourcePixel:Int,DestPixel:Int
Local Xpos:Int,YPos:Int
Local Red:Int,Green:Int,Blue:Int
Local Alpha:Float
Local AlphaMask:Int=$000000FF

'Demo
Repeat
	'Interaction/autorotate
	If MouseDown(1) Then Magnification:+0.01
	If MouseDown(2) Or KeyHit(KEY_SPACE) Then Magnification:-0.01
	Angle:+1
	If Angle>=360 Then Angle:-360
	If Angle<0 Then Angle:+360

	'Setup
	Y_UAdd=Cos(Angle+90)*Magnification
	Y_VAdd=Sin(Angle+90)*Magnification
	X_UAdd=Cos(Angle)*Magnification
	X_VAdd=Sin(Angle)*Magnification
	XCenter=MouseX()
	YCenter=MouseY()
	Y_U=-(Y_UAdd*OpHeight2)-(X_UAdd*OpWidth2)
	Y_V=-(Y_VAdd*OpHeight2)-(X_VAdd*OpWidth2)
	
	'Draw it
	PixmapWin.Paste(BackgroundPixmap,0,0) 'Draw background
	DestBase=Int Ptr(PixmapPixelPtr(DestPixmap,0,0)) 'reset top left
	For Local Y:Int=0 To OpHeight-1
		X_U=Y_U
		X_V=Y_V
		For Local X:Int=0 To OpWidth-1
			XPos=XCenter+Int(X_U)
			YPos=YCenter+Int(X_V)
			If XPos>=0 And XPos<OpWidth 'clip x
				If YPos>0 And YPos<OpHeight 'clip y
					'SourcePixel=( SourceBase + XPos + (YPos*SourcePixelsPerRow) )[0] 'Read full pixel, clipped
					SourcePixel=ReadPixel(SourcePixmap,Xpos,Ypos)
					SourcePixel=(SourcePixel Shl 8) | (SourcePixel Shr 24) 'Convert ARGB to RGBA if using ReadPixel()
					
					Alpha=(SourcePixel & AlphaMask)/256.0
					Red=(SourcePixel Shr 24) * Alpha
					Green=((SourcePixel Shl 8) Shr 24) * Alpha
					Blue=((SourcePixel Shl 16) Shr 24) * Alpha
					Alpha=1.0-Alpha
					DestPixel=DestBase[X] 'Read full pixel
					Red:+((DestPixel Shr 24) * Alpha)
					Green:+(((DestPixel Shl 8) Shr 24) * Alpha)
					Blue:+(((DestPixel Shl 16) Shr 24) * Alpha)
					DestBase[X]=(Red Shl 24) | (Green Shl 16) | (Blue Shl 8) 'Plot RGBA
				EndIf
			EndIf
			X_U:+X_UAdd
			X_V:+X_VAdd
		Next
		DestBase:+DestPixelsPerRow 'next row
		Y_U:+Y_UAdd
		Y_V:+Y_VAdd
	Next
	DrawPixmap PixmapWin,0,0
	Flip 1
Until KeyHit(KEY_ESCAPE)

As from this thread about it (refer there if you find any issues)
http://www.blitzbasic.com/Community/posts.php?topic=59342

Feel free to use as you wish. I used ReadPixel to read the bitmap but otherwise am writing using direct pointer access which is probably the fastest way. There might possibly be faster algorithms out there but this is pretty reasonable and runs at several frames per second on my machine. Note that a lot of the time, however, is taken to do DrawPixmap(). That command is only to let you look at what has been output and is not needed for rotating the pixmap. To test speed, take that out and call the routine a number of times checking the millisecs elapsed.

You don't need the background pixmap - it's just pasting a background image into the pixmap before drawing the rotate image on top of it.

It supports alpha blending using the alpha channel and also doubles as a zoom operation - just like the oldschool `rotozoomers`! The only thing it doesn't do is antialiasing. You could render it at twice the size to a pixmap and then do your own simple `filtering`, combining the contents of a 2x2 pixel area and average it into one destination color.

Btw in OpenGL if your screen is 32-bit with an alpha channel you CAN read the backbuffer's alpha channel, so long as when you draw the object you only use SOLIDBLEND (ie no blending or masking or alphablend) and writing to the destination alpha is enabled. I use writing to the alpha channel in my blobby-objects engine.


Jesse(Posted 2007) [#10]
this is something alot simpler. it draws a rotated image directly to a pixmap clipped. maybe not what you are looking for but it might help someone. it needs a 300x300 image to work. it may be modified to work with any size image.
Graphics 640,480

image:TPixmap = LoadPixmap("image.png")
Local size# = 1.0
Local pixmap:TPixmap = CreatePixmap(320,240,image.format)
ClearPixels pixmap
While Not KeyDown(Key_escape)
	Local nx%,ny%
	For Local y# = -99 To 100
		For Local x# = -159 To 160

			nx = ((x * Cos(angle)*(1.0/size) - y * Sin(angle)*(1.0/size)) + 150.0) 
			ny = ((x * Sin(angle)*(1.0/size) + y * Cos(angle)*(1.0/size)) + 150.0) 	
			
			If nx >= 0 And nx < 300
				If ny >=0 And ny < 300
					Local color:Int =ReadPixel( image,nx,ny)
					nx = x+159
					ny = y+120
					WritePixel pixmap, nx,ny,color
				EndIf
			EndIf

		Next
	Next

	DrawPixmap pixmap,640/2 - 320/2,480/2-200/2
	Flip(0)
	angle = (angle+2) Mod 360
	
Wend



Grey Alien(Posted 2007) [#11]
Hey GREAT! Thanks very much you two. The second one sounds like it may do the job. I'll see what it looks like, I'm keen to see what the edges of the rotated object look like.


Grey Alien(Posted 2007) [#12]
Jesse: Just tried out your code but it doesn't copy the alpha info across unfortunately.

AngelDaniel: OK yours appears to be doing something, but it's way too complex for me to get to grips with. Have you made any of this into some handy functions like RotatePixmap() or anything?

Basically I want to do this:

1) Get an image.
2) rotate it in software so I have a permanent copy of the rotation for drawing later. It would be handy to specify the rotation point instead of always central.
3) note: the new rotated image must be totally visible and not clipped inside the original image rectangle. OR allow me to specify a destination pixmap size and it could be clipped inside that.

Make sense?

Can anyone help please?


Grey Alien(Posted 2007) [#13]
btw, I've google for rotate pixmap (and pixmap rotation) and AngelDaniels code (in another thread) is the closest I've found.


ImaginaryHuman(Posted 2007) [#14]
As a function - it's not that much different...(untested)

Function RotatePixmap(SourcePixmap:TPixmap,DestPixmap:TPixmap,Angle:Float=0,Magnification:Float=1.0,XCenter:Float,YCenter:Float)
   'Draw a pixmap into another pixmap rotated and zoomed around a center with alpha blending
   'SourcePixmap is the source TPixmap to read from
   'DestPixmap is the dest TPixmap to write to
   'XCenter,YCenter is the center within the destination, usually half of its width and height

   'Source and destination pixmaps MUST be RGBA 32-bit
   If PixmapFormat(SourcePixmap)<>PF_RGBA8888 Then SourcePixmap=ConvertPixmap(SourcePixmap,PF_RGBA8888)
   If PixmapFormat(DestPixmap)<>PF_RGBA8888 then DeskPixmap=COnvertPixmap(DestPixmap,PF_RGBA8888)

   'What sizes are we working with?
   Local SourcePixelsPerRow=PixmapPitch(SourcePixmap)/4
   Local DestPixelsPerRow=PixmapPitch(DestPixmap)/4
   Local OpWidth:Int=PixmapWidth(SourcePixmap) 'Width of operation
   Local OpHeight:Int=PixmapHeight(SourcePixmap) 'Height of operation
   Local OpWidth2:Int=OpWidth/2
   Local OpHeight2:Int=OpHeight/2
   'Local XCenter:Int'=OpWidth/2
   'Local YCenter:Int'=OpHeight/2

   'Set up variables
   Local Y_UAdd:Float,Y_VAdd:Float,X_UAdd:Float,X_VAdd:Float
   Local Y_U:Float,Y_V:Float,X_U:Float,X_V:Float
   Local SourceBase:Int Ptr=Int Ptr(PixmapPixelPtr(SourcePixmap,0,0))
   Local DestBase:Int Ptr
   Local SourcePixel:Int,DestPixel:Int
   Local Xpos:Int,YPos:Int
   Local Red:Int,Green:Int,Blue:Int
   Local Alpha:Float
   Local AlphaMask:Int=$000000FF

   If Angle>=360 Then Angle:-360
   If Angle<0 Then Angle:+360

   'Setup
   Y_UAdd=Cos(Angle+90)*Magnification
   Y_VAdd=Sin(Angle+90)*Magnification
   X_UAdd=Cos(Angle)*Magnification
   X_VAdd=Sin(Angle)*Magnification
   Y_U=-(Y_UAdd*OpHeight2)-(X_UAdd*OpWidth2)
   Y_V=-(Y_VAdd*OpHeight2)-(X_VAdd*OpWidth2)
	
   DestBase=Int Ptr(PixmapPixelPtr(DestPixmap,0,0)) 'reset top left
   For Local Y:Int=0 To OpHeight-1
      X_U=Y_U
      X_V=Y_V
      For Local X:Int=0 To OpWidth-1
         XPos=XCenter+Int(X_U)
         YPos=YCenter+Int(X_V)
         If XPos>=0 And XPos<OpWidth 'clip x
            If YPos>0 And YPos<OpHeight 'clip y
               'SourcePixel=( SourceBase + XPos + (YPos*SourcePixelsPerRow) )[0] 'Read full pixel, clipped
               SourcePixel=ReadPixel(SourcePixmap,Xpos,Ypos)
               SourcePixel=(SourcePixel Shl 8) | (SourcePixel Shr 24) 'Convert ARGB to RGBA if using ReadPixel()
					
               Alpha=(SourcePixel & AlphaMask)/256.0
               Red=(SourcePixel Shr 24) * Alpha
               Green=((SourcePixel Shl 8) Shr 24) * Alpha
               Blue=((SourcePixel Shl 16) Shr 24) * Alpha
               Alpha=1.0-Alpha
               DestPixel=DestBase[X] 'Read full pixel
               Red:+((DestPixel Shr 24) * Alpha)
               Green:+(((DestPixel Shl 8) Shr 24) * Alpha)
               Blue:+(((DestPixel Shl 16) Shr 24) * Alpha)
               DestBase[X]=(Red Shl 24) | (Green Shl 16) | (Blue Shl 8) 'Plot RGBA
            EndIf
         EndIf
         X_U:+X_UAdd
         X_V:+X_VAdd
      Next
      DestBase:+DestPixelsPerRow 'next row
      Y_U:+Y_UAdd
      Y_V:+Y_VAdd
   Next
End Function

I think this should work. Actually ReadPixel() is not really supposed to be needed because I am trying to use pointers all the way through, but I recall having some difficulties with it so had to revert to reading using ReadPixel, which is not optimium speed. You can see the pointer-based read line is commented out. Maybe we can figure out how to make it work, then it should be even faster.

This routine only has to calculate the `angle` using trigonometry once outside the loop so isn't doing it for every pixel like that other routine you saw from someone else.

Also you need to be aware that there are two different ways to do rotation and they have different properties. Basically to rotate something, either the source or the destination has to be `aligned` to the axis, ie non rotated, while the other has to be aligned to the rotation angle. So there are two choices for how to move through the pixels. Either:

a) You go through all the pixels in the source pixmap horizontally and vertically as rows and colums, and then calculate what position those pixels would be when rotated to the destination. Pixels are then drawn into the destination at their rotated positions. With this method, you only read pixels that are in the source image and don't have to do any clipping of the source, but you have to be careful to clip the rendering into the destination because the corners might overlap the edges of the pixmap. This routine also will only draw to the destination those pixels which are a part of the source pixmap, and will ignore the areas outside of the rotated shape. The disadvantage of this method is that you get some `bittiness` in the quality of the rotation due to the way the coordinates are rounded and it's possible to get small holes in the drawn image. This is (I think) solved in the second method:

b) You go through all the pixels in the destination pixmap horizontally and vertically in rows and columns, and then calculate what rotated position to *read from* in the source pixmap. So as you go through the destination rows you rotate the coordinates around and then instead of reading the source one row at a time you are literally reading from a rotated rectangle within the source, and you read from the source diagonally. Pixels are drawn to the destination a row at a time and all pixels in the destination are drawn. The care then needs to be taken that you don't *read* pixels in the source image which are outside of the source pixmap, so you have to kind of clip it in reverse. When the rotated coords are outside of the source image, the dest pixel is not drawn. This type of rotation is used when you want to think of the destination pixmap as a kind of `viewport`, whereas the first method sees the source pixmap as the viewport.

These are two ways to do the rotation. Zooming is done much the same in both - the steps that are used to move throught the data are just scaled.

The function above uses the second method. Based on this code, since there is no detection of what size the destination pixmap needs to be to fit the rotated rectangle inside perfectly, you need to create a destination pixmap before calling. Whatever size you choose for the dest pixmap determines whether parts of the rectangle will be clipped off the edges.

Also with regard to defining the center position, if I remember rightly, normally the center should be half the size of the width and height of the destination pixmap. This results in the source pixmap being put into the middle of the destination image. If you move the center, it still rotates around the middle of the destination image but it reads from a different position in the source. So it might seem to be the `inverse` of where you actually place the center. So you might want to play with that and maybe define your center as CenterX=WidthOfDestPixmap-CenterX and CenterY=HeightOfDestPixmap-CenterY so that it actually appears to rotate around the coordinates you're defining. The object will still rotate around the center of the dest pixmap I think, I don't remember for sure.

With this routine you can zoom right into the source pixmap so that one pixel covers the entire destination, or vice-versa, zoom out so that the whole source image only fills one pixel in the destination.

Filtering that you see when you zoom in close is basically nearest-neighbor, which is the same as using a normal image (not FILTEREDIMAGE) - there's no bilinear filtering. Boundaries between pixel rows/columns are not antialiased. Render zoomed to a big pixmap and use a simple filtering algorithm to reduce it down to the size you want with an averaging effect to simulate `multitampling` antialiasing.

Let me know if you have questions.


Grey Alien(Posted 2007) [#15]
WOW, I don't think I have any quesions. That was an excellent post. I look forward to trying this out. Method b) certainly sounds the best. I used a similar method a while back in a BlitzPlus demo to fill up a rectangle with a sine wave plasma.

OK I'll play with the rotation centre point as basically I have an arrow and want it to rotate from it's base. I'll set the destination pixmap to be big enough to fit the rotated arrow at any angle. Then I can draw it clipped into my "manual" view port, cool.

Yeah I guess it won't be anti-aliased/filtered so I'll have to start looking into the code for that at some point.

Thanks again.


ImaginaryHuman(Posted 2007) [#16]
You're welcome dude.

When looking into filtering all you really need to do is, say, draw it twice as big, then for every group of 2x2 pixels you add up their values on a per-component basis and divide the total by 4, then write out the pixel to a regular sized pixmap.

There are more advanced ways to do it also, like adding `weights` to each of the pixels in the block, especially if you had a 3x3 or 4x4 block to work with, and some methods even get into fancy things like gausian bell shaped weighting and stuff like that. Or if you want to look into bilinear type filtering you could interpolate the pixel color across the area between each source pixel.

Enjoy.


Grey Alien(Posted 2007) [#17]
cool, well the 2x2 method sounds nice and easy :-)


ImaginaryHuman(Posted 2007) [#18]
Otherwise it is possible to somewhat modify the algorithm so that it does the filtering for you on the fly. While it's going through the destination pixels drawing stuff, it could be reading the source image with a higher resolution *as if* it were going to output to a larger zoomed pixmap, but instead of writing more pixels it would do the filtering on that group of source reads and combine it into a single output pixel.


Grey Alien(Posted 2007) [#19]
yes I see what you mean.


Jesse(Posted 2007) [#20]
try this one out see what you think



<edited> fixed a bug


ImaginaryHuman(Posted 2007) [#21]
I dont quite understand what that's doing. ???


Jesse(Posted 2007) [#22]
it is creating an image from a pixmap including the alpha. I believe. if that is not the case maybe I don't know what alpha is. it should be able to take any size pixmap(unverified) and convert it to an image. maybe some hiden documentation for loadimage?

and if you want to crate animated images from a pixmap:



Grey Alien(Posted 2007) [#23]
Jesse, there's a lot of constants in there that make it hard to understand. If you were to convert them to variables it might make it clearer what to do because my "image.png" is probably not the same size as yours and you've hardcoded in certain sizes there.


Jesse(Posted 2007) [#24]
it should work with any size image. I have tried different size images and it works fine. note that it does all of the creation in memory. you will only see any result a few seconds after all converting is completed. I will revise it and document it. Give me about an hour or two (busy at the moment).


ImaginaryHuman(Posted 2007) [#25]
I think I understand it. It's rotating a pixmap and drawing it in another. But it does not look very efficient. It's doing quite a lot of computation on a per-pixel basis with calls to Cos and Sin for every single pixel. That's not needed. You should only use per pixel trig if you are changing the rotation angle on a per pixel basis.


Jesse(Posted 2007) [#26]
yes IH you are correct, I did not try to optimize. I was just generalizing. Here is a slightly better optimized code. with some documentation. although I am having trouble with the scale factor. not shure it works correctly.
what it does, it draws several rotated image:tpixmap into another larger pixmap(one pixel at a time) placing them in a strip format then it uses loadanimimage to convert the pixmap to an animatedimage of type Timage . I noticed that the conversion is quite slow and the larger the images get the slower it gets on the bmax side as well as on my program side(duh). I did optimization up to what my skills allow. I hope the documentation helps. best I can do.

[edited] optimized a little more


Grey Alien(Posted 2007) [#27]
Jesse: thanks for doing that, I'll take a look.