kim@analog.UUCP (Kim Helliwell) (12/11/87)
I am interested in any information anyone may have on algorithms for rotating bitmaps by +/- 90 degrees. I am aware of two-- the obvious brute-force method of grabbing a pixel and swapping indices (with an appropriate translation) before stuffing it into another bitmap, and the parallel blitter version which was described in an issue of MacTutor a couple of years ago. I have been thinking about the relative speeds of these two, and I have come up with the following analysis of the relative speeds: the brute force method obviously goes as n*n, where n is the number of pixels on a side of the bitmap. the blitter version actually goes as n*n*log(n), because the log(n) steps required and the n*n memory read/writes required by the blitting operation. The log() means log base two, of course. What this tells me is that the blitter version is only a win if you have a hardware blitter (with lots of parallism, for example, and DMA). For use with CopyBits, it appears to be a lab curiosity. Or is there something I am missing? In any case, I have tried both methods, and neither of them are anywhere close to as fast as MacPaint. I am interested in speeding things up, and want to know if anyone knows how to do things faster? I admit that I haven't done much to optimize things to date. Would doing the thing in straight assembly code be the only thing I need to do to speed it up? (I am writing it in C at present). Or is there something really clever that Paint does? Any leads or help will be appreciated. Kim Helliwell hplabs!analog!kim
oster@dewey.soe.berkeley.edu (David Phillip Oster) (12/12/87)
In article <600@analog.UUCP> kim@analog.UUCP (Kim Helliwell) writes: >I am interested in any information anyone may have on algorithms for >rotating bitmaps by +/- 90 degrees. In the summer, Dr. Dobb's magazine of software tools said that they were planning a graphics issue and that outlines for articles could be posted to a certain computer mail account. Well, I sent them an article outline and got no reply, even though I logged in to the same machine they had an account on to post. My article was on this very topic. The article in MacTutor refers to a scheme described in the first Smalltalk-80 book. This scheme works well if all CopyBits took the same time, independent of their arguments. The scheme I use is: divide the picture to be rotated into 8x8 pixel blocks. (Pad the edges to make them come out exactly on block boundaries.) Now the destination coordinates of each block are pretty obvious: example: a b c d i e a e f g h => j f b i j k l k g c l h d The last remaining step is to take one of these 8x8 blocks and rotate it. The following C code is a schema for how you do that. for(i = 0, destBit = 1; i < 8; i++, destBit *= 2){ src = src8[i]; for(j = 0, srcBit = 0x80; j < 8; j++, srcBit /= 2){ if(src & srcBit){ /* if the srcbit is on */ dest8[j] |= destBit; /* turn the dest bit on */ }else{ /* else */ dest8[j] &= ~destBit; /* turn the dest bit off */ } } } Of course, you'd use plenty of register variables, and replace the explicit array referencing here with pointer arithmetic. Then you copybits from the offscreen bitmap where you have been doing the work back to the screen. I've programmed this up, and it _is_ fast. --- David Phillip Oster --A Sun 3/60 makes a poor Macintosh II. Arpa: oster@dewey.soe.berkeley.edu --A Macintosh II makes a poor Sun 3/60. Uucp: {uwvax,decvax,ihnp4}!ucbvax!oster%dewey.soe.berkeley.edu
ali@rocky.STANFORD.EDU (Ali Ozer) (12/12/87)
In article ... oster@dewey.soe.berkeley.edu.UUCP (David Phillip Oster) writes: >In article <600@analog.UUCP> kim@analog.UUCP (Kim Helliwell) writes: >>I am interested in any information anyone may have on algorithms for >>rotating bitmaps by +/- 90 degrees. >The scheme I use is: divide the picture to be rotated into 8x8 pixel >blocks. (Pad the edges to make them come out exactly on block boundaries.) I've included (at the end of this article) the 8x8 rotation code, in assembly, written by Tomas Rokicki (rokicki@sushi.stanford.edu). It's lengthy, mainly because the loops are unrolled. It was written for the Amiga, but it's general enough such that with a little attention to how the parameters come in it should work on the Mac. And it's fast --- I use it to rotate 64x64 4 bit plane images. (But it's a CPU hog...) Also, when doing the rotation, no matter how large the bitmap you're rotating is, you only need ONE 8x8 temporary. Then you can do the rotation in the source area itself. Ali Ozer, ali@rocky.stanford.edu -------------------------------- /* Rotation code from Tomas Rokicki. ** If you use it, please leave the author's name intact! */ /* * This code flips an 8 x 8 rectangle to the right 90 degrees. * Note that the blitter could be up to 4 times faster (according * to my preliminary calculations), but it would probably also be * more complex, and this should be plenty fast. Also, for small * rectangles like 16 x 16, this will certainly be faster. */ /* * This subroutine takes four arguments, two for the source and * two for the destination. There is a pointer to the first byte * to be flipped, and the value to be added to the address for * each subsequent byte. */ flip(sp, sw, dp, dw) unsigned char *sp, *dp ; int sw, dw ; { /* 8(a5), 14(a5), 12(a5), 18(a5) */ #asm ; ; This subroutine actually flips the eight bytes in the lower byte ; of d0-d7, and sticks the results in the upper bytes of d0-d7, ; rotating their previous contents down. Don't let the length of ; it scare you; you might be able to discern a pattern in the ; instructions. It's really quite simple. ; movem.w reglist,-(sp) move.l 8(a5),a0 move.w 12(a5),a1 move.b (a0),d0 add.w a1,a0 move.b (a0),d1 add.w a1,a0 move.b (a0),d2 add.w a1,a0 move.b (a0),d3 add.w a1,a0 move.b (a0),d4 add.w a1,a0 move.b (a0),d5 add.w a1,a0 move.b (a0),d6 add.w a1,a0 move.b (a0),d7 roxr.b #1,d0 roxr.w #1,d0 roxr.w #1,d1 roxr.w #1,d0 roxr.w #1,d2 roxr.w #1,d0 roxr.w #1,d3 roxr.w #1,d0 roxr.w #1,d4 roxr.w #1,d0 roxr.w #1,d5 roxr.w #1,d0 roxr.w #1,d6 roxr.w #1,d0 roxr.w #1,d7 roxl.w #8,d0 roxr.b #1,d1 roxr.w #1,d1 roxr.w #1,d2 roxr.w #1,d1 roxr.w #1,d3 roxr.w #1,d1 roxr.w #1,d4 roxr.w #1,d1 roxr.w #1,d5 roxr.w #1,d1 roxr.w #1,d6 roxr.w #1,d1 roxr.w #1,d7 roxl.w #8,d1 roxr.b #1,d2 roxr.w #1,d2 roxr.w #1,d3 roxr.w #1,d2 roxr.w #1,d4 roxr.w #1,d2 roxr.w #1,d5 roxr.w #1,d2 roxr.w #1,d6 roxr.w #1,d2 roxr.w #1,d7 roxl.w #8,d2 roxr.b #1,d3 roxr.w #1,d3 roxr.w #1,d4 roxr.w #1,d3 roxr.w #1,d5 roxr.w #1,d3 roxr.w #1,d6 roxr.w #1,d3 roxr.w #1,d7 roxl.w #8,d3 roxr.b #1,d4 roxr.w #1,d4 roxr.w #1,d5 roxr.w #1,d4 roxr.w #1,d6 roxr.w #1,d4 roxr.w #1,d7 roxl.w #8,d4 roxr.b #1,d5 roxr.w #1,d5 roxr.w #1,d6 roxr.w #1,d5 roxr.w #1,d7 roxl.w #8,d5 roxr.b #1,d6 roxr.w #1,d6 roxr.w #1,d7 roxl.w #8,d6 roxr.b #1,d7 roxl.w #8,d7 move.l 14(a5),a0 move.w 18(a5),a1 move.b d7,(a0) add.w a1,a0 move.b d6,(a0) add.w a1,a0 move.b d5,(a0) add.w a1,a0 move.b d4,(a0) add.w a1,a0 move.b d3,(a0) add.w a1,a0 move.b d2,(a0) add.w a1,a0 move.b d1,(a0) add.w a1,a0 move.b d0,(a0) movem.w (sp)+,reglist reglist reg d0/d1/d2/d3/d4/d5/d6/d7/a0/a1 #endasm }
gillies@uiucdcsp.cs.uiuc.edu (12/13/87)
I think your analysis of the parallel bitblt algorithm is fallacious. The speed of a BitBLT EVEN IN ASSEMBLY LANGUAGE is approx n*n/16 or n*n/32, depending upon the word size of your machine. See [Deutsch84] to learn how to dynamically "compile" a bitblt request into machine language, then execute it like a bat out of hell, a type of self modifying (self-compiling?) algorithm. For the particular mac rotation problem, you could hand-code each of the 1..9 bitblt operations. The recursive Bitblt trick is outlined in the book "Smalltalk-80: The language and its implementation" pp 408-410. Using clever boolean ops and masks, you can do all the recursive BitBLTs in parallel hence you need only log(n) BITBLTs. Since the Mac screen is approx 512*512 dots, then a full screen BITBLT rotation should take: (n*n/16)*log(2,512) == (n*n/2) time. Thus the blitter algorithm should *always* outperform the simple-minded bit-by-bit transfer algorithm on the macintosh. [Deustch84] (E-mail me for the ref -- it's not here, or check ACM Guide) Don Gillies {ihnp4!uiucdcs!gillies} U of Illinois {gillies@p.cs.uiuc.edu}/* End of text from uiucdcsp:comp.sys.mac */
oster@dewey.soe.berkeley.edu (David Phillip Oster) (12/14/87)
In article <76000070@uiucdcsp> gillies@uiucdcsp.cs.uiuc.edu writes: >I think your analysis of the parallel bitblt algorithm is fallacious. > [much deleted, check it, it is interesting] You're posting shows the blind preference for elegance that I was critiquing in my article. Think it through: 1.) Here is the analysis again: The recursive version for rotating an NxN bitmap does O(log N) BitBlts to rotate a bitmap by 90 degrees. (It does three BitBlts to interchange quarters, then must recursively interchange those quarters all the way down. Each recursive step takes another three BitBlts.) In addition, the recursive algorithm only works directly for squares that are a power of 2 on edge. (To handle other sizes, you usually copy into an offscreen bitmap that is large enough, rotate it, and copy the result back.) The 8x8 transpose version does O(1) bitblt to rotate a bitmap by 90 degrees. In addition, it works directly for any rectangle that has edges that are a multiple of 8. 2.) by experiment. I have actually coded up and run both versions. For a 256 by 256 bitmap, the 8x8 transpose method was about 10 times faster than the recursive CopyBits method. (And, I didn't even optimize the transpose method.) The recursive method is elegant, but _slow_. The point of my article is that the elegant algorithms you learn to appreciate in an algorithms class sometimes really _are_ worse than using brute force in the real world. --- David Phillip Oster --A Sun 3/60 makes a poor Macintosh II. Arpa: oster@dewey.soe.berkeley.edu --A Macintosh II makes a poor Sun 3/60. Uucp: {uwvax,decvax,ihnp4}!ucbvax!oster%dewey.soe.berkeley.edu
kim@analog.UUCP (Kim Helliwell) (12/17/87)
In article <76000070@uiucdcsp>, gillies@uiucdcsp.cs.uiuc.edu writes: > > I think your analysis of the parallel bitblt algorithm is fallacious. > The speed of a BitBLT EVEN IN ASSEMBLY LANGUAGE is approx n*n/16 or > n*n/32, depending upon the word size of your machine. See [Deutsch84] > to learn how to dynamically "compile" a bitblt request into machine > language, then execute it like a bat out of hell, a type of self > modifying (self-compiling?) algorithm. For the particular mac rotation > problem, you could hand-code each of the 1..9 bitblt operations. > > ... > ... > > (n*n/16)*log(2,512) == (n*n/2) time. > > Thus the blitter algorithm should *always* outperform the > simple-minded bit-by-bit transfer algorithm on the macintosh. I think you are the one with the fallacious argument. Factors like 16 or log(some constant) are irrelevant for purposes of analyzing CPU time requirements. My original analysis was sketchy, but here it is in a slightly expanded form: Suppose that I wish to rotate an N X N bitmap. If I do it the brute-force way, it will take N*N operations (where an operation is defined as a memory transfer with a masking operation thrown in somewhere (to get at the right bit of a particular byte. Ok, have it your way--to get at the right bit of a long word). If I use the recursive bitBLT version, it will take log(N) steps (this is obviously a log base 2), and each step consists of several bitblt operations. I counted 15 for the version I was using, but I suppose that this could be reduced for by some clever rewrite of the blit operation. I won't quibble about the factor of 15--it's irrelevent when compared to the powers of N we are working with. So let's suppose that we somehow magically manage to do the whole thing with 1 blit per step(!) What is it we are doing? We are moving some bytes (OK, longwords) from one place to another in memory, again with a masking operation thrown in. In the real algorithm, many of the blit's involved moving only half or a quarter of the full bitmap, but again this is a factor that is irrelevant. I figure that I gave up the factor of 15, so it's only fair for you to concede the factor of 4 or 2, and we'll say for the purposes of argument that the one blit per recursion involves the whole bitmap, which is N*N/(8,16,or 32). The constant factors are ignorable by the principal already enunciated, so I get the the blit version is O(N*N*log(N)). Why do we ignore the constant factors? Because, although in specific cases you could show that for certain values of N, and for specific versions of the two algorithms, that the blit version would win, the thing that is relevant is what happens as N increases. At some point, N*N*log(N) will become bigger than N*N, NO MATTER WHAT CONSTANT FACTORS MULTIPLY THEM. That is why there is so much discussion of sorting in the literature discussing relative merits of various algorithms for various sizes of problem--often a less overall efficient algorithm for a specific case is a win because the size of the problem and the speed of the comparison fits the method better. BTW, the brute force method wins in another way. That is, it can be used to rotate non-square bitmaps and non-integral values of N, which can be a big win if you want to rotate a long narrow strip (which I did).
gillies@uiucdcsp.cs.uiuc.edu (12/22/87)
In article <>, kim@analog.UUCP writes: > I think you are the one with the fallacious argument. Factors like >16 or log(some constant) are irrelevant for purposes of analyzing > CPU time requirements. > > My original analysis was sketchy, but here it is in a slightly expanded > form: > > Suppose that I wish to rotate an N X N bitmap. If I do it the > brute-force way, it will take N*N operations (where an operation is > defined as a memory transfer with a masking operation thrown in somewhere (to > get at the right bit of a particular byte. Ok, have it your way--to get at > the right bit of a long word). >..... When you analyze an algorithm whose running time is dominated by the size of its output (n*n in the rotation algorithm), it is appropriate to do a low-level Knuth-type analysis, or at least attempt to do it. There are many ways to speed up a "hopelessly slow" bit algorithms by exploiting the binary parallelism 16, 32, or 64-bit boolean and arithmetic operations. For instance, you can count the bits in a 32-bit computer word in 5 iterations of one loop (O(log(wordsize)) time). Obviously, ANY algorithm that rotates an n*n bitmap must take O(n*n) time to do its output, unless you use a degenerate representation (like storing all 4 rotations of a bitmap, and just returning a pointer in O(1) time). You wanted some reasons why Atkinson's rotation algorithm might run so fast. You referred to a particular algorithm on a particular machine with a particular screen size. And things are not as it seems: Using some elementary analysis for the particular case, it looks like the BITBLT algorithm is still a win over the brute force method. Of course, as another notewriter pointed out, you can get even more clever in doing the rotation by breaking the screen into 8*8 blocks. This method *still* does not "sound" like its asymptotic performance equals the dumb brute force algorithm, but I bet it runs faster on any particular commercial processor you choose for a benchmark. Constants count. Don Gillies {ihnp4!uiucdcs!gillies} U of Illinois {gillies@p.cs.uiuc.edu}