How to capture a rotated image
BlitzMax Forums/BlitzMax Beginners Area/How to capture a rotated image
| ||
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. |
| ||
Function GrabImage( image:TImage,x,y,frame=0 )or Function GrabPixmap:TPixmap( x,y,width,height ) if you need to save it. |
| ||
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?) |
| ||
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. |
| ||
Gah! |
| ||
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? |
| ||
Probably best to just develop a rotation algo using read/write pixel to the pixmap and then saving it. |
| ||
JGOware: Yes you are probably right. |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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? |
| ||
btw, I've google for rotate pixmap (and pixmap rotation) and AngelDaniels code (in another thread) is the closest I've found. |
| ||
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. |
| ||
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. |
| ||
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. |
| ||
cool, well the 2x2 method sounds nice and easy :-) |
| ||
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. |
| ||
yes I see what you mean. |
| ||
try this one out see what you think <edited> fixed a bug |
| ||
I dont quite understand what that's doing. ??? |
| ||
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: |
| ||
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. |
| ||
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). |
| ||
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. |
| ||
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 |
| ||
Jesse: thanks for doing that, I'll take a look. |