[comp.sys.mac.programmer] How do you make 'offscreen bitmaps'?

vrm@blackwater.cerc.wvu.wvnet.edu (Vasile R. Montan) (04/08/91)

Whenever anyone posts a question on comp.sys.mac.programmer about
animation, the customary response is to use 'offscreen bitmaps'.
While I have no problem understanding this technique in theory,
I'm not sure exactly how to implement it in fact.

Exactly what sort of 'offscreen' structure am I drawing into?
Is it the type actually defined as BitMap?  If so, how do I SetPort
to draw in it, since SetPort accepts a GratPtr as its arguement?
Or is it a GrafPort I am drawing into?  If so, how do I tell the
Toolbox that the GrafPort is off screen?

I presume that CopyBits is the preferred way to put the offscreen
drawing on the screen.

--Kurisuto
un020070@vaxa.wvnet.edu

russotto@eng.umd.edu (Matthew T. Russotto) (04/09/91)

In article <1553@babcock.cerc.wvu.wvnet.edu> un020070@vaxa.wvnet.edu writes:
>Whenever anyone posts a question on comp.sys.mac.programmer about
>animation, the customary response is to use 'offscreen bitmaps'.
>While I have no problem understanding this technique in theory,
>I'm not sure exactly how to implement it in fact.
>
>Exactly what sort of 'offscreen' structure am I drawing into?
>Is it the type actually defined as BitMap?  If so, how do I SetPort
>to draw in it, since SetPort accepts a GratPtr as its arguement?
>Or is it a GrafPort I am drawing into?  If so, how do I tell the
>Toolbox that the GrafPort is off screen?
>
>I presume that CopyBits is the preferred way to put the offscreen
>drawing on the screen.

For black and white animation, you draw into a grafport.  You create a BitMap
structure of the appropriate size, create a port, and call SetPortBits to
associate the bitmap with the port.  Copybits can be used, but if you really
want to be a speed demon, you can probably optimize each transfer by aligning
things right and writing tight assembly code.  My guess is that you would
definitely want to bypass the trap dispatcher to get speed, if you do use
CopyBits (or, perhaps, BlockMove).  (call NGetTrapAddress to get the address
of the routine into a function pointer, and call that function)
--
Matthew T. Russotto	russotto@eng.umd.edu	russotto@wam.umd.edu
     .sig under construction, like the rest of this campus.

captkidd@athena.mit.edu (Ivan Cavero Belaunde) (04/10/91)

In article <1553@babcock.cerc.wvu.wvnet.edu> un020070@vaxa.wvnet.edu writes:
>Whenever anyone posts a question on comp.sys.mac.programmer about
>animation, the customary response is to use 'offscreen bitmaps'.
>While I have no problem understanding this technique in theory,
>I'm not sure exactly how to implement it in fact.
>Exactly what sort of 'offscreen' structure am I drawing into?
>Is it the type actually defined as BitMap?  If so, how do I SetPort
>to draw in it, since SetPort accepts a GratPtr as its arguement?
>Or is it a GrafPort I am drawing into?  If so, how do I tell the
>Toolbox that the GrafPort is off screen?

The offscreen structure you draw into is either a GrafPort, a CGrafPort,
or a GWorld. GrafPorts are B/W, CGrafPorts are color, and GWorlds are a
mixed beast (more on this later, but note you need 32-bit QD to do
GWorlds. They do make dealing with offscreen bitmaps a lot easier).
You will need a BitMap or PixMap that you allocate yourself (although
the OS will allocate yone for you in certain situations), and a buffer
for the actual pixmap/bitmap data (which again you may have to create
yourself, but not always).
Basically the procedure is to create the bitmap or pixmap, set it up for
drawing into it (using a port), draw into it, and CopyBits it into your
destination port.

If you're creating a bitmap, it's pretty simple. At the start all you need to
know is the size of the image, which you use to initialize a BitMap
structure. The bounds field is just a rect describing the size of the image.
The rowBytes field is simply the horizontal size in pixels divided by eight
and rounded up. Note that it must be even (so you might need to round up to
the next word). baseAddr is just a pointer to a buffer you allocate with
NewPtr (or a locked dereferenced handle), whose size is just the vertical
size of your image * rowBytes.
Then if the current port is a GrafPort (as opposed to a CGrafPort, which
you can check by looking at the most significant bit of the rowBytes field,
either thePort.portBits.rowBytes or theCPort.portVersion) you save the
previous BitMap from it (thePort.portBits) and call SetPortBits. The you
can draw to your heart's content in your BitMap and once you're done restore
the old BitMap and do CopyBits. Minor caveat: in theory, the current port's
clipRgn and visRgn could interfere with your drawing in it. I haven't had
any problems, but I haven't used this technique as much as the others, so
you might want to go ahead a create your own GrafPort, like you would have
done if the current port was a CGrafPort, as follows:
Allocate space for a GrafPort (either in the stack of via NewPtr) and call
OpenPort to set it up. Then SetPortBits to the bitmap you allocated, and
go ahead and draw on it. Make sure you save the old port before calling
OpenPort, however. Once you're done drawing, restore the port and copy
the bitmap onto the screen (or wherever). When disposing the port, call
ClosePort first to get rid of the visRgn and clipRgn and then deallocate
the port itself and the bitmap.

Color. Color can be ugly. Color can be a bad time. But it looks a lot cooler,
so you might want to do it anyway. The easiest way is to use 32bitQD and
the GWorld functionality. But if you don't want to (because you don't
want to require 32bitQD or you want direct access to the pixel data
without having to muck around in 32-bit MMU mode), read on.

With 32bit QD (yeah, it's easier):
Call NewGWorld to allocate an offscreen graphics world and graphic device
of whatever depth you want. Save the old GWorld, SetGWorld to the new one,
lock the pixels down and draw in it. CopyBits it wherever to your heart's
content and DisposeGWorld once you're done.Way nice. The only bad thing here
is that if you want to have direct access to the RAM where the pixels are
sitting you have to play with the MMU mode. Lock the pixels down, call
GetPixBaseAddr (which returns the 32-bit mode address of the pixel data),
shift into 32-bit mode (pay attention to technotes re: MMU mode swapping
and the program counter) muck with the pixels, and switch back to 24-bit
MMU mode. Problem here is that you shouldn't risk calling any toolbox routines
at all while in 32-bit mode unless you are on a 32-bit clean machine, so
you're seriously limited in what you can do. Of course, you can always
check if the pixel data is in 24-bit accessible memory beforehand (the MSB
of the address would be 0, I believe), and if it's not go to 32-bit MMU mode,
copy it down to a buffer in main RAM, downshift to 24-bit mode, and access
the pixel data there.  There's all sorts of ugliness involved. I want my
32-bit clean ROMs. Ack.

But I digress...

Without 32bit QD:
The reason this is so much more complicated is mostly because of generality.
You might have a 24-bit deep screen but would like to create a 8-bit offscreen
pixmap, for example, and since when creating a color port the new port takes
its characteristics from the current one, there are complications associated
with creating an offscreen GDevice and other ugliness.
The easiest way is to save the current device using GetGDevice, get the
bit depth of the deepest screen you'll be drawing into using GetGDevice and
examining the PixMap, set it to be the current device, allocate a port
using OpenCPort. Then modify th portPixMap's fields as required by your image
(resize the bounds rect and rowBytes and allocate the buffer), as well as the
port's portRect. SetPort to it and draw in it.

If you want to keep your offscreen pixmap to a fixed bit depth (say 8 bits)
regardless of the bit depth of the graphic devices, there's some ugliness
involved because of the inverse tables and stuff. Basically instead of finding
out which GDevice to use you'll have to create your own offscreen GDevice
and initialize it yourself. Part of this initialization involves creating an
inverse table from the CLUT you want to use (using MakeITable), so that if
you CopyBits into your offscreen buffer from a different bit depth or CLUT
QD will be able to adjust the colors. Once you have your offscreem GDevice
you can SetGDevice to it and create the offscreen port and pixMap as above and
draw in it. When initializing the device, it's useful to know that
GetCTable (bitDepth) returns the system default CLUT for that bit depth.

Moral: Use 32-bit QD if you're going to do weird bit depth stuff.

>I presume that CopyBits is the preferred way to put the offscreen
>drawing on the screen.

Yup. Make sure you SetPort to the destination port (ie the screen) before
copybitsing into it so that if there's varying bit depths and whatnot it
can compensate by using the graphics device's inverse table.

Oh, one more thing. I seem to remember that one of  Apple's Mac Sample Code
Packages (14?) has code on offscreen stuff.

Hope this helps,

-Ivan Cavero Belaunde
Visualist
Digital Video Applications Corp. (DiVA)
Internet: captkidd@ATHENA.MIT.EDU