need to rotate a pixmap with smooth filtering
BlitzMax Forums/BlitzMax Programming/need to rotate a pixmap with smooth filtering
| ||
...in software :) I know I post a lot but its not as if I won't share the results when I see someone else ask! So is there a blindingly fast way to rotate stuff like in Blitz+ for the purposes of pre-calculating frames? I want to do a rotation of a 128x128 image 64 times (with alpha channel) and stick it down on a pixmap and save it. I know its big but this will be for software rendering, I think 4 meg isn't much really system ram wise... Let me know if there is a fast and filtered way of doing it in pixmaps with alpha - thanks! |
| ||
sounds pretty complex with alpha, good luck! |
| ||
Thanks! Yep It will be for pre-rendering a list of frames, not for in-game use. |
| ||
I've got some c++ code to do this, in fact it'll do it pretty well in real-time. Remind me tomorrow and I'll dig it out for you (I'm off to bed now). |
| ||
Hey thanks Tim - much appreciated! Will this work with bmax? |
| ||
It won't work straight off the Bat but with a few tweaks it will. |
| ||
It's probably faster to upload your image as an Image once, let Max2D do the smooth filtering and rendering and rotating and alphablending for you, which is done in hardware, and use Grab Pixmap for each frame. |
| ||
Below is a BlitzBasic 2 (Amiga) sourcecode to do rotation using my old Mildred graphics library for reading and writing pixels. You might be able to adapt it. Note that most of it is stuff you can cut out, the actual rotate itself is fairly small. Also notice how much more complicated BlitzBasic USED to be ;-D Commands starting with M are 3rd party Mildred library commands - let me know if you have questions as to what they do. It shouldn't mess up with what you want to do. ".q" types were `quick` types, a 16:16 fixed point format for decimal numbers. You could just use Floats. I'm not sure if BlitzMax has `Pi` .. it's just Pi.. you know... 3.14 etc. MPlot plots a pixel (like Plot or WritePixel). MPoint reads a pixel (like ... oh, Max doesn't have that, lol ... ReadPixel()?) This routine is the type where it draws all pixels within a rectangle of destination screen space, regardless of whether some parts of the rotated image are off-screen or outside the rectangle. I think that is what you'll want to use considering you're rotating within a pixmap. If not put a blank border around your source image and make your pixmap bigger to accomodate. This is also a rotozoomer - you can zoom in and out as well as rotate. That said, to do antialiasing/smoothing you should probably draw to a rather large pixmap while scaling up quite large, and then scale down the results using a simple filtered resizing technique - filtering, smoothing, averaging, whatever. To handle alpha just read the alpha value of each pixel that you are reading from (the MPoint() calls), divide it by 256.0 to get in the range 0-1.0, and then multiply your pixel's R G and B components by that amount to scale it. Good luck. I still think it would be faster and easier to use: LoadImage SetScale SetAlpha SetRotation DrawImage GrabPixmap SavePixmapPNG Plus it'll do the filtering for you with high accuracy and speed - except maybe the edges wouldn't be antialiased? I commend you on your efforts to do software rendering, something close to my heart having written Mildred back in the day to do that very thing (256-colors chunky routines for BlitzBasic 2 on the Amiga). I never did get around to implementing rotation in the library itself but the below routine was what I was working towards. ... My problem with implementing it on the Amiga? .... trying to do it in assembler, I ran out of registers. ;-) buf.b=0 its.l=0 cnt.b=0 ang.q=0 degrad.q=Pi/180 mag.q=1 ResetTimer While Joyb(0)<>3 AND Joyb(1)=0 If Joyb(0)=1 Then mag+0.01 If Joyb(0)=2 Then mag-0.01 ang+1 If ang>=360 Then ang=-360 If ang<0 Then ang+360 y_uadd.q=Cos((ang+90)*degrad)*mag y_vadd.q=Sin((ang+90)*degrad)*mag x_uadd.q=Cos(ang*degrad)*mag x_vadd.q=Sin(ang*degrad)*mag xcenter=SMouseX ycenter=SMouseY y_u.q=-(y_uadd*(opheight/2))-(x_uadd*(opwidth/2)) y_v.q=-(y_vadd*(opheight/2))-(x_vadd*(opwidth/2)) For y=0 To opheight-1 x_u.q=y_u x_v.q=y_v For x=0 To opwidth-1 MPlot x,y,MPoint(xcenter+x_u,ycenter+x_v,1) x_u+x_uadd x_v+x_vadd Next x y_u+y_uadd y_v+y_vadd Next y Taken from this original demo program... |
| ||
WOW! You used amiblitz too? I cut my game-making teeth on that stuff! :) Thank you for your help Angel and Indie, I will look into all your options... gonna be tricky I think. I should point out this is for a software render mode so I can't use max2D :) Amiblitz really was a lot more complicated. Times have moved on haven't they? |
| ||
You still have Mildred code lying around? Cool! (And slightly disturbing...) |
| ||
Yah, back in the days. I started out on Amos - Game Creator, that thing was a classic but unfortunately quite slow by todays standards and not kept up to date except for Amos `Professional`, then I moved onto BlitzBasic 2. I think they now have some kind of product called AmiBlitz now which is a community effort, or something. Yeah I have all of the Mildred code, source, demo programs, website, documentations etc. 30,000 lines of assembler code ;-) Funny to see the in-line asm in that program up there, too. That was a neat capability for the orignal Blitz. And yes, very slightly disturbing that I still cherish that precious code archive, even though it's more or less useless now. I don't think that code above will be too hard to modify. I understand about not using Max2D, I guess it would defeat the purpose. The code probably would almost work as it stands. You just need to add the alpha stuff. I'm thinking though that perhaps ultimately you want the kind of rotate that does things the other way around, where it draws only the pixels that are in the rotated rectangle, ie a source-based shape, rather than a destination-based shape as in the above code. But maybe you can adapt it. FlameDuck: Hey, I think I have quite a few bits of code that YOU wrote for Mildred Mr Duck ;-) Guilty as charged. Ahhhhhhhhhh, old times. |
| ||
Here is the BlitzMax version of the rotozoomer demo. However, it is not working right. I get what seems to be different colors in X but in Y the same color is repeated down the screen. I cannot figure out where the bug is. Can anyone help? OEJ: Should be easy enough to adapt this to what you want, once it's working, it handles alphablending, rotation, zoom, a background image, and the rotation angle can be a Float. Anyone see the bug? (My images are just 640x480 images) I am quite certain it has nothing to do with the alphablending. Even with bare minimum of two lines - WritePixel(ReadPixel()) it is still wrong. '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=PixmapWidth(SourcePixmap) Local DestPixelsPerRow=PixmapWidth(DestPixmap) Local OpWidth:Int=128'PixmapWidth(SourcePixmap) 'Width of operation Local OpHeight:Int=128'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)+Max(aa+bb,256)) 'Make alpha to see effect if image doesn't have one Next Next 'Set up variables Local Angle:Float=0 Local Magnification:Float=1.0 Local Y_UAdd,Y_VAdd,X_UAdd,X_VAdd:Float Local Y_U,Y_V,X_U,X_V:Float Local SourceBase:Int Ptr=Int Ptr(PixmapPixelPtr(SourcePixmap,0,0)) Local DestBase:Int Ptr=Int Ptr(PixmapPixelPtr(DestPixmap,0,0)) Local SourcePixel,DestPixel:Int Local Red,Green,Blue:Int Local Alpha:Float 'Demo Repeat 'Interaction/autorotate If MouseDown(1) Then Magnification:+0.01 If MouseDown(2) 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 DestPixmap.Paste(BackgroundPixmap,0,0) 'Draw background For Local Y:Int=0 To OpHeight-1 X_U=Y_U X_V=Y_V For Local X:Int=0 To OpWidth-1 SourcePixel=Int Ptr(SourceBase + Min(Max(0,XCenter+X_U),OpWidth) + ((Min(Max(0,YCenter+X_V),OpHeight) * SourcePixelsPerRow)))[0] 'Read full pixel, clipped 'SourcePixel=ReadPixel(SourcePixmap,XCenter+X_U,YCenter+X_V) Alpha=(SourcePixel Shr 24)/256.0 Red=((SourcePixel Shl 8) Shr 24) * Alpha Green=((SourcePixel Shl 16) Shr 24) * Alpha Blue=(SourcePixel & $FF) * Alpha Alpha=1.0-Alpha DestPixel=(DestBase+X+(Y*OpWidth))[0] 'Read full pixel, clipped 'DestPixel=ReadPixel(DestPixmap,X,Y) Red:+(((DestPixel Shl 8) Shr 24) * Alpha) Green:+(((DestPixel Shl 16) Shr 24) * Alpha) Blue:+((DestPixel & $FF) * Alpha) (DestBase+X+(Y*DestPixelsPerRow))[0]=(Red Shl 24) | (Green Shl 16) | (Blue Shl 8) 'Plot RGBA 'WritePixel(DestPixmap,X,Y,(Red Shl 24)|(Green Shl 16)|(Blue Shl 8)) 'Write RGBA 'WritePixel DestPixmap,X,Y,SourcePixel X_U:+X_UAdd X_V:+X_VAdd Next Y_U:+Y_UAdd Y_V:+Y_VAdd Next DrawPixmap DestPixmap,0,0 Flip 1 Until KeyHit(KEY_ESCAPE) |
| ||
Can anyone tell me why exactly, these lines do not work:Y_UAdd=Cos(Angle+90)*Magnification Y_VAdd=Sin(Angle+90)*Magnification X_UAdd=Cos(Angle)*Magnification X_VAdd=Sin(Angle)*Magnification Even if I take out the *Magnification, it seems that Y_UAdd, Y_VAdd and X_UAdd are all 0's whilst X_Vadd is the only one that has a value. When I do: Print Cos(Angle+90) Print Sin(Angle+90) Print Cos(Angle) Print Sin(Angle) I see valid numbers for all variables. But if I try to PUT those numbers into a variable they turn into 0's, even if I change the variables to Doubles. What gives? Is this a bug? |
| ||
Oh...You're gonna kick yourself... ;o)Local Y_UAdd,Y_VAdd,X_UAdd,X_VAdd:Float Local Y_U,Y_V,X_U,X_V:Float Should be... 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 |
| ||
These are declared as Int... Local Y_UAdd,Y_VAdd,X_UAdd,X_VAdd:Float except X_VAdd |
| ||
Is that strange! *kicks self, lightly* I thought you could separate variables by comma's and if you define the type on the last one then it applies to all of them. ???? I guess that might explain why a list of Int's doesn't produce an error but floats do - because the others default to int's? Well. Will have to change this as soon as I get home and try it out. I presume the demo now works for you, with that modification? (Thanks Lenny!) |
| ||
I get garbled/corrupt display. |
| ||
The next thing to do with this would be to turn the loops inside out, ie the X and Y counters should be set outside the loop and with an X_Add and Y_Add increment, which steps through the source pixmap in non-rotated space, and then the X_U,X_V and Y_U,Y_V gets translated to loop counters in the For-Next loops. At the moment you're looking at a non-rotated viewport into a rotating world, whereas what you probably want is a rotated viewport into a non-rotated world. .. then you can set the rotation angle and have it draw all of the pixels from the source pixmap, and only those pixels, at the rotated angle like Max2D does. I think that should be simple enough. Then all that's left to add is filtering. I guess you can't really draw the rotated image to some buffer in a big size and then scale it down as it would be more efficient to somehow filter it on the fly. I guess it totally depends what kind of filtering you want - what algorithm. Different levels of antialiasing, ie 2x2, 4x4, 8x8 etc? You'd have to probably accumulate the values for all the pixels in the square and divide by total number of pixels (use a Shift!), and then plot that pixel? That would at least be antialiased, but I'm not sure how it'd look when zoomed up big .... not quite the same as bi/trilinear filtering/interpolation. I suppose you could interpolate from the color of one pixel to the next based on what decimal portion of the source pixel you are at. |
| ||
Try removing my custom read/writes code and just use the ReadPixel() WritePixel() calls.. see if that works .. .I noticed some kind of difference with that. When you say garbled is that beacuse I added a part that draws random alpha values to each pixel in the source pixmap? Do you at least see rotation of the garble? |
| ||
One Eyed Jack: I think you might find that doing rotation, with alpha blending, is probably not going to be quite as fast as you might hope for compared to Blitz+ .... The alpha blending itself is kind of expensive and does slow things down. If add filtering in there it's going to get even slower. That said, maybe it's actually pretty fast but it just seems slow due to the DrawPixmap() call which is always slow. Would be interesting to time the loop without the DrawPixmap() or Flip 1. |
| ||
Do you at least see rotation of the garble? The garble rotates within, what looks like , it's own little viewport. |
| ||
Ok good. Perhaps the pixmap is corrupt, or we just need decent alpha values. I presume it zooms in and out with the mouse buttons? Yah, being within its own viewport is basically how the routine currently works. It needs to be turned inside out, to have the source counters made into the For-Next loop counters, and put the X and Y as separate counters with add-variables. |
| ||
Hi Angel, thanks for trying. It's a huge challenge I think, and one thats possibly better of being done in C++? |
| ||
I don't know, I think it's simple enough. It'd be interesting to see speed comparisons with C++ and BMax. I'll get the bugs out and see if I can turn it into a one where you take an image and draw the whole thing rotated. I am just thinking maybe there is a better algorith that does some kind of scanline business on the rotated polygon so as not to be missing pixels or plotting at scattered locations. [EDIT] Come to think of it that's probably not so difficult as it sounds. You could figure out the edges of the polygon that is to be drawn, sort the rows in Y order, then do one scanline at a time reading data from the source in diagonal lines. Not sure if it'd be more efficient given the extra work needed but who knows.[/EDIT] |
| ||
Here is the working rotozoomer. However, I had to remove my own speedier `read` of the source pixmap because in debug mode it works fine, in non-debug mode it crashes. It's something to do with maybe it's reading outside of the pixmap, but I have put a clip in place to prevent that, so I'm not sure what the bug is. Still ... this version should work.... alphablended rotozoomer. Left mouse button zooms out, right mouse button or space bar zooms in. '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) |
| ||
AngelDaniel: It looks *very* weird on my computer... any chance you can link to your background.jpg and rotozoom.jpg, or at least tell me what dimensions your source images are? When I try to run it with two of my own images, I see a static copy of the background image in the upper left corner, cut to the size of the rotozoom.jpg image, with a 'blind' effect of malformed horizontally aligned lines super imposed over it... Only every 13th line (?!) shows, its stretched horizontally, and the colors of the rotozoom.jpg are way off as well. (it has ni light blue in it). The lines do animate, but only horizontally... Scroll from the right to the left, and change in appearance on the way. See here for a sample: ATI Radeon 9600 Pro |
| ||
Use anything up to a 640x480 bitmap for both images in 24-bit color. It should work. I have just used two nature images of that size. Try with widths that are multiples of 8 or something. |
| ||
One Eyed Jack: You don't have to use my code by any means, but I was thinking about the different kinds of rotates and how you might make use of them. There are two kinds: Either you touch every pixel in the destination viewport horizontally and vertically, and at each pixel lookup the rotated coordinates of a pixel in the source image which you copy over ... sort of a `read from` rotation. Then there is the one where you go through the source image horizontally and vertically and copy each pixel to a rotated destination coordinate, sort of a `write to` operation. The code above is the first type where all pixels in the viewport are assessed for whether they contain valid source coordinates, and currently only draws those pixels that are within the source image, nothing outside it. It ocurred to me that a better algorithm would be a scaline algorithm like a polygon fill routine, where you do a whole row at a time. Then it occurred to me that the above routine is very close to that. It is actually drawing horizontal scanlines. So my idea is, for each object that you want to draw, basically give its own `rotation viewport`, the size of which is a bounding box just large enough to contain all of its rotated pixels (and you can re-size it as it rotates). Then use the above routine to draw that pixmap rotated within that viewport. Because the whole thing fits into the viewport at the rotated angle you'll know that you can see all of it, and because it clips to only draw the source pixels whose coordinates are within the source pixmap you'll know it's only drawing those pixels and not anything outside it. To the end user it will *LOOK LIKE* the same results you'd get from a routine that is the other way around - ie reading source data horizontally/vertically and drawing to rotated coords, except the reading and writing of the destination is horizontally/vertically aligned. I think it actually would be faster this way because you have to READ the dest buffer to do an alpha blend as well as write the final values to it, and these two accesses would be more efficient when you're going across a whole row rather than trying to splatter the screen with diagonal lines which might miss some pixels and leave holes. Then to draw the next object you just work out the new bounding box which is your new viewport into rotation space, and call the rotation routine again with that viewport. You might be able to optimize through some kind of math, the skipping of pixels that are outside the source pixmap when it is rotated, ie trace diagonal lines until it collides with the edge of the pixmap without having to do every pixel on the line. But otherwise I think this should be fairly quick. The reason this demo is slow is mainly because of DrawPixmap() which is slow. I'm not sure how you're going to get around that for a software renderer unless you are opening your own display and getting direct access to video mem. So anyway, I am not going to work on this any more, gotta get back to my game project. But good luck with it. I think filtering should be an easy addition - store the variables of the previous pixel loop for the next one and then use a combination of those pixels with the current pixel, the ratio of which is the decimal part of the coordinates and the inverse of that. [EDIT] ie, if for example the rotation routine says that the current pixel is read from 27.41,12.13, then you know take the value of that pixel and multiply it by .41, then take the value of the pixel next to it and multiply that by the inverse 1.0-.41=.59, and add them together. Do the same in Y (little bit trickier to code) where you take .13 of the current pixel and .87 of the next pixel (current pixel is the blended pixel you just worked out) and then add those together. All together you then divide by 4 (shr 2) to get the average color for each component. Something like that. Good luck with it. Or just use some other code ;-D |
| ||
Thanks man, you're a lifesaver. I have already learned so much from this code :) It's not what I expected rotation code to look like. Thanks for sharing and just how come you're so helpful? :) |
| ||
You are welcome. I am helpful because I am generous and I like to be of service and when somebody has a need and I know something that can help I like to share it in a teaching sort of capacity. It makes me feel good. Giving is receiving. I have learned from the code also, you see, because by trying to teach someone something and share something you also become a student. Truth be told I was curious to see it work as well because it was sort of where I left off with my Mildred graphics library so I was curious to see it working in Max. I didn't originally author the code/algorithm, truth be told, ie the actual rotation part. I didn't know how to do it and had to ask some chaps on an old Amiga IRC channel who came up with the basic loops and formulas. I think I now understand it better than I did back then. I'm still a bit miffed that my SourcePixel= line only works in debug mode and not regular mode but ReadPixel() isn't too much slower and works with debug off. Maybe you can fix it. It's really fairly simple, all you are doing is almost the same thing as a non-rotated copy of all the pixels from one pixmap to another. The only difference is that instead of finding the next pixel right to the right of the current one, you have to go up or down a bit of a slope. From what you know of using Cos() and Sin() to draw a circle, ie X=Radius*Cos(Angle) and Y=Radius*Sin(Angle), all it's doing is using this to change what would be a horizontal read into reading from a pixel that is at a given angle. Of course, to accomodate that you have to have a separate pair of X and Y variables to define the pixel, which is why it has four variables to handle the loop rather than just X and Y. The X loop which proceeds from left to right in the source image, instead of assuming that Y=whatever the Y loop currently is, has to also increment/decrement its own Y counter so that it moves on a diagonal. And the same with the Y loop, to move on a diagonal rather than straight down assuming X=0 you have to add an extra X counter to it. So you end up with an outside loop that has an X and Y counter with an adder value for each, and an inner loop that has the same. It's fairly simple really. The alphablend is pretty basic, just split the color into its components and multiply by alpha and combine with the dest components multiplied by the inverse of that alpha. Nice thing is you can handle rectangular pixmaps. Sometimes rotozoomers used to only handle powers of 2 and would wrap around. This code could be easily enough adapted to wrap around the source texture so that the entire viewport is always full with repeated copies of the pixmap ... just add something like `XPos:Mod OpWidth` and `YPos:Mod OpHeight` just after they are set within the loop, and you'll get your pixmap repeated to fill the whole viewport. You can also add an extra multiply to the Alpha component, with a `Fade:Float` value from 0 to 1.0, ie `Alpha:*Fade` after reading the Alpha pixel value and before multiplying the components by it. This will let you fade the source pixmap in and out like a `global alphablend` applied to the whole image that simulataneously alphablends the individual pixels based on their alpha values. But that wouldn't be a direct feature replacement for Max2D as Max2D can't do that. Another feature you could add is very easily done - have an XMagnification and a YMagnification instead of just one mag for both. If these differ, you can stretch the image horizontally and/or vertically by different amounts at the same time as zooming and rotating. I think Max2D should have that feature, it would be easy enough to add a paramter to SetScale, but alas. :-D The reason that it is detecting presses of the spacebar is because on the Mac with a one-button mouse you don't have a right mouse button to press ;-D Let us know how you get on. |
| ||
I find it weird, I thought I should mention, that in Max ReadPixel and WritePixel require ARGB values, whereas there is no PF_ARGB8888 format for pixmaps. What's the point? It would be better and more consistent and useful to return RGBA and to write RGBA by default. Otherwise they should provide a PF_ARGB8888 format. Notice how I had to rearrange the pixel format on the fly within the main loop after using ReadPixel() - a timewaster. In the demo code above I had to one moment be doing RGBA and the next ARGB (see the dummy alpha routine), a bit confusing at first. I also should explain that I used things like Green:+(((DestPixel Shl 8) Shr 24) * Alpha) with the two shifts, because I think this is faster than using an `and` with a mask. Shifts don't require a read of the mask from memory. Shifting left by 8 chops off the top 8 bits and sets them to 0, then shifting right by 24 moves it into the lowest byte ready for multiplying by alpha. I think two shifts is going to be faster than a an `and` and a shift. The mask would have to be an Int so doing $FF as an immediate value wouldn't ensure it would be included in the opcode at the assembler level. It might be worth your while moving the "DestPixel=DestBase[X] 'Read full pixel" line up a couple of lines to after the Green= line - just to possibly pipeline things a bit better - it can be reading the dest pixel while you're working on other stuff, rather than having to wait for the pixel to be read before the "Red:+((DestPixel Shr 24) * Alpha)" line has to be executed. Might help, dunno. You might convert the Floats into Doubles for more accuracy - I notice a few anomalies along edges of source rows/columns where you see a the column shift right, then left for one pixel, then right for one, then back left again which can make the image look `bitty`. But that will probably slow it down. I also don't know if the instances of DestBase[X] are as fast as using DestBase[0] and modifying the DestBase variable with DestBase:+1 in the loop. It also might be a little bit faster to put the whole alphablending stuff into a single line as part of the pixel write. That would cut out the saving of values into variables along the way. Also, the OpWidth and OpHeight don't have to be the size of the source pixmap, they can be smaller. e.g. 128x128. You could also do Flip 0 to gain a little bit ;-D |