TweetFollow Us on Twitter

Safe Dissolve
Volume Number:6
Issue Number:12
Column Tag:Programmer's Forum

Related Info: Quickdraw

QuickDraw-safe Dissolve

By Mike Morton, Honolulu, HI

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

A QuickDraw-safe Dissolve

I have sinned against you.

It was a long time ago. It was a brief dalliance. But it’s time to set things right.

On a hot August night in 1984, I was sitting in a basement with a 128K Mac and a Lisa running the Workshop development system. I had read Inside Macintosh about as far as the QuickDraw section on bitmaps and then bogged down. I didn’t want to learn about DITLs or the segment loader or any of that high-level junk. I was impatient and wanted some instant gratification. It seemed like you could get neat effects by directly manipulating screen memory, bypassing QuickDraw for considerable gains in speed.

So I wrote a subroutine called “DissBits”, modeled on the QuickDraw “CopyBits” call. It copied one bit image to another, moving the pixels one at a time in pseudo-random order. The resulting effect was a smooth dissolve, a “fade” in video lingo.

Persistence of vision

DissBits has popped up here and there over the years, which is pleasing and embarrassing. It’s embarrassing because Apple has been telling people from the outset not to assume things about the depth of the screen, and DissBits does that -- it won’t work in color, or with multiple monitors. I’ve even had it crash in the middle of a job interview.

The subroutine stubbornly tries to evolve with the times: John Love’s MacTutor series on graphics includes an updated version which handles multi-bit pixels -- but still has problems when the target spans multiple monitors. MacroMind has apparently also produced a color version, though they haven’t been very forthcoming about how they did it. Someone (I have no idea who) has produced a version for some IBM monitors. And in more general form, the algorithm is included in the recent compendium Graphics Gems (ed. Andrew Glassner, Academic Press, 1990).

Cleanliness is next to impossible?

Can you get the esthetics of a smooth dissolve without sneaking behind QuickDraw’s back and breaking all the compatibility rules? Well, to some degree, yes. This article presents a set of subroutines collectively called DissMask. This approach to dissolving images onto the screen directly manipulates an offscreen bitmap, but operates on the visible screen using only QuickDraw. While Apple can continue to define new layouts for screens, the structure of a one-deep bitmap is unlikely to change.

This technique isn’t good for fading large areas -- you can adjust the speed to some degree, but you’ll rarely want to use this for a full-screen fade. Still, it’s an instructive look at how closely the speed of a purist solution can approach that of a trickier, hardware-specific solution. It’s also immensely simpler than the original code, since it’s largely coded in C (the original was in assembler), and it solves a fundamentally smaller problem.

Through a mask, darkly

The new method copies the source image to the screen with CopyMask. (CopyMask was introduced with the Mac Plus ROM, so you’ll need to test that it’s present if you want your application to run on very old machines.) CopyMask transfers a selected portion of a source image to a destination image, using a “mask” bitmap to control which pixels are transferred. The dissolve is accomplished by doing a series of CopyMask calls, with more and more black pixels set in the mask. The final CopyMask is with a 100%-black mask (which, come to think of it, could be replaced by a CopyBits call).

The part of the code which most closely resembles the original dissolve is a function called dissMaskNext, which adds more black pixels to the mask bitmap. It’s much simpler than the dissolve code, though, since it only sets bits and doesn’t copy them. In addition, it works on a bitmap whose bounds are powers of two, and that reduces clipping done in the loop. In fact, the loop is just eight instructions per pixel for small images, but after each new round of adding black pixels, the CopyMask call consumes a lot of time, so in most cases the time for this “original” code is negligible.

How many pixels do you add to the mask between each CopyMask? That’s up to you. Adding too few pixels between copies will make the dissolve too slow and (if your image is large enough) may contribute to flicker. Adding too many pixels between copies will keep the dissolve from being smooth, since too many pixels will appear at a time. For large images, there may be no happy medium between these two, especially if other things slow down the CopyMask call: stretching, a target which spans multiple monitors, or a slow CPU.

That ol’ black (and white) magic

What are those magic constants in the array in dissMask.c? The heart of the dissolve is a strange algorithm which produces all integers from 1 through 2n-1 in pseudorandom order. Here’s a glimpse of how it works. (If you want a more detailed discussion, see the December 1985 MacTutor [reprinted in Best of MacTutor, Vol. 1], the November 1986 Dr. Dobb’s Journal, or the above-mentioned Graphics Gems.)

Consider this strange little loop:

/* 1 */

int value = 1;
do
{ if (value & 1)
    value = (value >> 1) ^ MASK;
  else value = (value >> 1);
} while (value != 1);

Each iteration throws the lowest bit off the right end by shifting, but also XORs in a magic MASK constant if the disappearing bit was ‘1’. Look at some values for the constant MASK, and the sequence of values produced for each one.

MASK Sequence produced

0x0003 1 3 2

0x0006 1 6 3 7 5 4 2

0x000C 1 12 6 3 13 10 5 14 7 15 11 9 8 4 2

Each of these masks randomly produces 1 through 2n-1. For every 2n-1, there’s at least one mask to produce a sequence of this length. One list is given in the seqMasks[] array in dissMask.c; verifying it is left as an exercise for the truly bored reader.

The algorithm for DissMask works only with a bitmap whose extents are both powers of two (the mask for CopyMask can be larger than the source and destination). So the total number of pixels in the mask bitmap is always a power of two. The pixels can be numbered 0 to 2n-1, so the loop goes through the random sequence values 1..2n-1 and sets the bit for each value. Bit 0 has to be done as a special case.

If the mask is less than 64K pixels, the loop in dissMaskNext() does several things for each iteration: the first four instructions map the sequence value to a bit address and set the bit. The next three instructions generate the next sequence element. The DBRA at the end of loop just limits the number of sequence values used up per call, since the mask is supposed to get only a little bit darker for each call, not chase through the whole sequence and become completely black.

Using DissMask

Dissolving uses several steps. (Sample code to do this is in the “dissolve” function in main.c, the example program.) You should #include “dissMask.h” to define the types and functions you need. Declare a variable of type “dissMaskInfo”, which is used for both internal purposes by the routines and to return some information to you.

When you have the image you want to copy, call dissMaskInit, passing it the bounds rectangle of the source image and a pointer to the dissMaskInfo structure. This will initialize everything and fill in the structure. It may fail (by running out of memory, for example), in which case it’ll return FALSE. If this happens, you should just call CopyBits and give up.

The initialization code will allocate a mask bitmap and store a pointer for it into the info structure. Your code will use this bitmap in calls to CopyMask. It will also store “pixLeft”, a count of the number of black pixels left to set in the mask bitmap. Your code will watch this count, which decreases with each call to dissMaskNext, and stop looping after it reaches zero.

Before beginning the main loop, you’ll usually want to hide the cursor to speed things up. The main loop is just:

/* 2 */

while (info.pixLeft)
{ dissMaskNext (& info, STEPS);
  CopyMask (srcBits, & info.maskMap, dstBits,
           srcRect, & info.maskRect, dstRect);
}

The value of STEPS is up to you -- it can be a constant, a function of the size of the bitmap (as given by the original value of info.pixLeft), or whatever. One approach would be to time the first couple of iterations and adjust the pixel-steps per iteration to try to calibrate the total time for the dissolve. I haven’t tried this kind of mid-course correction -- my lazy approach was to just use “steps = (info.pixLeft/20)” to get a constant number of loops (20, in this case).

When you’re done call ShowCursor if you hid it before the loop, and call dissMaskFinish to deallocate the bitmap.

Summary

The big advantage of this revised approach is the generality: there’s no intimate knowledge of the layout of the screen, and QuickDraw can begin supporting chunky/planar, smooth or even puréed pixels without breaking this code.

The big disadvantage is the time to dissolve large images. It’s up to you to decide how much flicker is acceptable before you switch to some other effect. Remember that you should try your application on different CPUs and monitors unless you have clever timing code to make sure the speed is right.

Optimizing CopyMask might be one way to help the speed -- does aligning the two images and the mask help? What else affects the speed of CopyMask? (I don’t know anyone want to research this?)

You may also want to synchronize CopyMask calls with the monitor’s vertical refresh to reduce flicker. For a large image, the copy may take longer than the sweep down the screen, so this can be difficult.

There are also some interesting variations that wouldn’t work with the original dissolve. Starting with the Mac II ROMs, CopyMask can stretch images, so this code can, too. If CopyMask is extended to do transfer modes, this code will too. [But if the transfer modes are additive or have some other side effect, the mask bitmap would have to be erased between iterations.] You can also do some tricks with using dissMaskNext() to create and store several masks, and use them non-sequentially, allowing an image to fade in and out (“Beam me up, Scotty!”).

But the big lesson is that there are penalties for playing by the rules, typically in speed and esthetics, and that there are compatibility penalties for end-running the system. The brave new world of Color QuickDraw has broken a lot of applications, and each new version of the system software may to do the same. I feel strongly that compatibility and playing by Apple’s rules are important, but I’m glad I didn’t feel that way in 1984. It sure was fun.

Acknowledgements

Many thanks to Ken Winograd, Rich Siegel, Martin Minow, and David Dunham for their advice and comments.

Listing:  dissMask.h

/*
 dissMask.h -- Copyright © 1990 by Michael S. Morton.  All rights reserved.
 dissMask is a set of functions which allow you to perform a digital 
“dissolve” using a series of calls to QuickDraw’s CopyMask() function. 
 These functions don’t do anything graphical directly; they just enable 
you to do so by rapidly generating a sequence of masks.
 Advantages of this scheme include:
 • It’s completely hardware-independent.  Because QuickDraw does the 
actual graphics work, the only low-level work is done on an old-fashioned 
bitmap, the format of which is stable.
 • This means (same advantage, continued) that it works at any bit depth 
and with multiple-monitor targets.
 • Because the client gets to choose how many black pixels are added 
to the mask in between copies, they can to some degree control the speed 
of the dissolve.  The client can empirically measure the time for the 
first copy, and do “mid-course correction” to get the desired speed.
 • On Mac II ROMs and later, stretching works.
 Disadvantages include:
 • Low memory may prevent allocating the bitmap.
 • The maximum speed can be obtained only by reducing the number of CopyMask 
calls, making the appearance “chunky”.
 • Large areas dissolve slowly and with a lot of flashing.
*/

#include “MacTypes.h”/* for Rect type */
#include “QuickDraw.h”  /* for BitMap type */

typedef struct { 
 /*PUBLIC section: these vars are read-only for the client */
 BitMap maskMap;
 /* mask bitmap for client to pass to CopyMask */
 Rect maskRect; 
 /* mask rectangle for client to pass to CopyMask */
 unsigned long pixLeft;  
 /* number of bits remaining to copy */

 /*PRIVATE section: */
 unsigned long seqElement; /* current element of sequence */
 unsigned long seqMask;  /* mask used to generate sequence */
} dissMaskInfo;   /* define this type */

/* dissMaskInit -- Initialize a “dissMaskInfo” structure, based solely 
on the bounds of the source rectangle.  This is the same rectangle passed 
to CopyMask() as the “srcRect” parameter.
 There will be a slight increase in performance if the srcRect’s dimensions 
are each near or equal to (but not over) a power of two.  This increase 
will be more noticeable if there are fewer CopyMask() calls.
 Under certain conditions, the function may fail, in which case it returns 
FALSE. These include running out of memory and the case where the source 
rectangle is ridiculously small.  The client should just do a vanilla 
CopyBits() call if a failure is reported.
 In addition to filling in the dissMaskInfo structure with the BitMap 
and Rect for use with CopyMask(), the “pixLeft” field is set up.  This 
is the count of pixels left to turn black in the mask.  It MAY be more 
than the number of pixels in the specified rectangle.  The client should 
advance the mask until this field is zero. */
extern Boolean dissMaskInit (Rect *srcRect, dissMaskInfo *info);

/* dissMaskNext -- “Advance” the mask bitmap by the specified number 
of pixels. Advancing in small steps will cause a slower, smoother dissolve. 
 Advancing in large steps will cause a faster, less smooth effect.
 You should hide, or at least obscure, the cursor before entering the 
loop with the CopyMask() calls.
*/
extern void dissMaskNext (dissMaskInfo *info, unsigned long steps);

/* dissMaskFinish -- Clean up the internals of the information struct. 
 Always call this when the client is done. */
extern void dissMaskFinish (dissMaskInfo *info);
Listing:  dissMask.c

/* dissMask.c -- Copyright © 1990 by Michael S. Morton.  All rights reserved.
 History:
 23-Aug-90- MM - First public version.
 Enhancements needed:
 • Can CopyMask be used with transfer modes with side effects (e.g., 
additive)?
 If so, we should erase the bitmap before setting each new set of bits. 
 */
#include “dissMask.h”

/* Internal prototypes: */
static void round2 (short *i);

/* Masks to generate the pseudo-random sequence.  The table runs 0 32, 
but only elements 2 32 are valid. */
static unsigned long seqMasks [ /* 0 32 */ ] =
{0x0, 0x0, 0x03, 0x06, 0x0C, 0x14, 0x30, 0x60, 0xB8, 0x0110, 0x0240, 
0x0500, 0x0CA0, 0x1B00, 0x3500, 0x6000, 0xB400, 0x00012000, 0x00020400, 
0x00072000, 0x00090000, 0x00140000, 0x00300000, 0x00400000, 0x00D80000, 
0x01200000, 0x03880000, 0x07200000, 0x09000000, 0x14000000, 0x32800000, 
0x48000000, 0xA3000000
};

extern Boolean dissMaskInit (srcRect, info)
 Rect *srcRect; /* INPUT: bounds of source rectangle */
 register dissMaskInfo *info; 
 /* OUTPUT: state-of-the-world for mask */
{
 /*Copy the client’s source rectangle, then normalize it to (0,0) for 
simplicity. */
 info->maskRect = *srcRect; /* copy it  */
 OffsetRect (& info->maskRect,/*  and normalize it  */
 -info->maskRect.left, -info->maskRect.top); 
 /*  to (0,0) at the top left */

 /*Round it up to a power of two in each dimension for the bitmap’s bounds. 
 This speeds up the dissolve considerably by removing bounds checking. 
 Also, ensure that the width of the bitmap is a multiple of two bytes, 
or 16 bits. */
 info->maskMap.bounds = info->maskRect;
 /* start by copying the client’s bounds */
 round2 (& info->maskMap.bounds.bottom); 
 /* now round both extents  */
 round2 (& info->maskMap.bounds.right); 
 /*  up to powers of two */
 if (info->maskMap.bounds.right < 16)
 /* too small to be a bitmap? */
 info->maskMap.bounds.right = 16;  /* yep: round it up */

 /*Compute total number of pixels in the mask bitmap; initialize the 
countdown counter. */
 info->pixLeft = info->maskMap.bounds.bottom * (long) info->maskMap.bounds.right;
 /*Figure magic mask to be used in dissMaskNext() loop: */
 { register short log2 = 0; /* log base-two of pixel count */
 register unsigned long ct; 
 /* working copy of pixel count */

 ct = info->pixLeft;
 while (ct > 1)   /* until log2(ct) == 0 */
 { ct >>= 1; ++log2; }/*  shift down one; bump log2 */

 /*Actually, I don’t think either of these (<2 or >32) can happen  */
 if ((log2 < 2) || (log2 > 32))  
 /* outside of table bounds? */
 return FALSE;
 /* can’t do this; client should CopyBits() */
 info->seqMask = seqMasks [log2];  
 /* set up mask which generates cycle of len 2**log2 */
 }

 /*Because we count iterations, we needn’t watch the sequence element: 
it can start anywhere. */
 info->seqElement = 1;
 /* init sequence element to any nonzero value */

 /*Finish filling in pixmap; handle allocation failure. */
 info->maskMap.rowBytes = info->maskMap.bounds.right / 8;
 info->maskMap.baseAddr = NewPtrClear (info->pixLeft / 8); 
 /* allocate data for bitmap */
 if (! info->maskMap.baseAddr) /* allocation failed? */
 return FALSE; /* tell client[should we clear MemErr?] */

 --info->pixLeft; /* kludge: one element done outside loop */
 return TRUE; /* client can continue */
} /* end of dissMaskInit () */

extern void dissMaskNext (info, steps)
 register dissMaskInfo *info; 
 /* UPDATE: state-of-the-world for mask */
 register unsigned long steps;
 /* INPUT: number of steps to take */
{register unsigned long element, mask; /* for use in asm{} */
 register void *baseAddr; /* for use in asm{} */

 if (steps == 0) steps = 1; /* keep things sane */
 if (steps > info->pixLeft) /* more steps than we need? */
 steps = info->pixLeft;   /* yes: just go ’til the end */
 info->pixLeft -= steps;
 /* debit this before we trash “steps” */

 element = info->seqElement;/* move these  */
 mask = info->seqMask;    /*  memory-based variables  */
 baseAddr = info->maskMap.baseAddr;
 /*  into registers for asm {} */

 --steps; 
 /* set up counter to run out at -1, not 0, for DBRA rules */

 /*If all the arithmetic can be done in 16 bits, we do so: */
 if ((info->seqMask & 0xffff) == info->seqMask)
 asm
 { /* Sixteen-bit case: “.w” operands and a simple DBRA to wind it up. 
*/
 @loopStart16:
 /*Set the bit for the current sequence element: */
 move.w element, D0/* copy bit number  */
 move.b D0, D1   /* and make a copy for numbering within a byte */
 lsr.w  #3, D0 /* convert bit number to byte number */
 bset   D1, 0(baseAddr, D0.w) 
 /* set D1’st bit in D0’th byte of contiguous bitmap */

 /*Advance to the next sequence element.  If this element was 1, we’re 
all done. */
 lsr.w  #1, element /* throw out a bit  */
 bcc.s  @skipXOR16  
 /*  if it’s only a zero, don’t XOR */
 eor.w  mask, element 
 /* if a one-bit fell off, flip mask-bits */
 @skipXOR16:
 dbra   steps, @loopStart16 /* count down steps */
 }

 else asm
 { /* Thirty-two-bit case: “.l” operands and a SUB.L to follow up the 
DBRA: */
 @loopStart32:
 /*Set the bit for the current sequence element: */
 move.l element, D0/* copy bit number  */
 move.b D0, D1 
 /* and make a copy for numbering within a byte */
 lsr.l  #3, D0   
 /* convert bit number to byte number */
 bset   D1, 0(baseAddr, D0.l) 
 /* set D1’st bit in D0’th byte of contiguous bitmap */

 /*Advance to the next sequence element.  If this element was 1, we’re 
all done. */
 lsr.l  #1, element/* throw out a bit  */
 bcc.s  @skipXOR32 
 /*  if it’s only a zero, don’t XOR */
 eor.l  mask, element
 /* if a one-bit fell off, flip mask-bits */
 @skipXOR32:
 dbra   steps, @loopStart32 
 /* count down low word of steps */
 sub.l  #0x00010000, steps
 /* low word ran out: check high word */
 bpl.s  @loopStart32 
 /* still   0: loop some more */
 }

 info->seqElement = element;
 /* update sequence element from asm{}’s changes */

 if (! info->pixLeft) /* all general pixels copied? */
 asm    /* yes: there’s a special case */
 { bset #0, (baseAddr)
 /* element 0 never comes up, so set bit #0 in byte #0 */
 }
} /* end of dissMaskNext () */

extern void dissMaskFinish (info)
 register dissMaskInfo *info; 
 /* UPDATE: struct to clean up */
{DisposPtr (info->maskMap.baseAddr);
 info->maskMap.baseAddr = 0L; /* lights on for safety */
} /* end of dissMaskFinish () */

/* round2 -- Round a value up to the next power of two. */
static void round2 (i)
 register short *i;
{register short result;

 result = 1;
 while (result < *i)
 result <<= 1;
 *i = result;
} /* end of round2 () */
Listing:  main.c

/* Quick and dirty demo application for dissMask routines.
 Written August 1990 by Mike Morton for MacTutor. */
#include “dissMask.h”

/* “dissolve” is just a part of the driver; you’ll probably want your 
own code to call the dissMask____ functions(), although your code will 
look a lot like “dissolve”. */
static void dissolve (BitMap *srcBits, BitMap *dstBits, Rect *srcRect, 
Rect *dstRect, unsigned short steps);

void main (void)
{Rect winBounds;
 WindowRecord theWindow;
 Handle scrapHandle;
 long scrapResult;
 long scrapOffset;
 char windowTitle [100];
 BitMap offScreen, winBits;
 short rows, cols;
 EventRecord evt;
 long dummy;
 short clipCopies, clipCount;
 PicHandle thePict;
 short picWidth, picHeight;
 Rect target;

 /*Standard Mac init, skipping menus & TE & dialogs: */
 InitGraf (& thePort);
 InitFonts ();
 FlushEvents (everyEvent, 0);
 InitWindows ();
 InitCursor ();

 /*We prefer to be run with graphics on the clipboard,
 but protest only feebly if there’s no PICT: */
 strcpy (windowTitle, “Dissolve demo using CopyMask”);
 scrapHandle = NewHandle (0L);
 scrapResult = GetScrap (scrapHandle, ‘PICT’, & scrapOffset);
 if (scrapResult < 0) /* no PICT available? */
 strcat (windowTitle, “ (NO PICTURE ON CLIPBOARD)”);
 CtoPstr (windowTitle);

 /*Steal main screen, inset a bit, and avoid menu bar. */
 winBounds = screenBits.bounds;
 InsetRect (& winBounds, 8, 8);
 winBounds.top += 20 + MBarHeight;

 /*Make up a new window: */
 NewWindow (& theWindow, & winBounds, windowTitle,
 true, /*visible at first*/ noGrowDocProc,
 -1L, /*frontmost*/ false, /*no go-away box*/
 0L); /*no refcon*/
 SetPort ((GrafPtr) & theWindow);
 winBits = thePort->portBits; 
 /* remember where onscreen bit image is */

 rows = thePort->portRect.bottom - thePort->portRect.top;
 cols = thePort->portRect.right - thePort->portRect.left;

 /*Make up a bitmap with the same bounds as the window. */
 offScreen.bounds = thePort->portRect;
 offScreen.rowBytes = (cols + 7) / 8;
 if (offScreen.rowBytes & 1)
 ++offScreen.rowBytes;
 offScreen.baseAddr =
 NewPtrClear (rows * (long) offScreen.rowBytes);
 if (offScreen.baseAddr == 0) /* out of memory? */
 { SysBeep (10); /* be uninformative */
 ExitToShell ();
 }

 /*Fill up the offscreen bitmap.  If we have a clipboard PICT, tile the 
offscreen bitmap with it; else fill the bitmap with black. */
 SetPortBits (& offScreen);
 if (scrapResult >= 0)
 { thePict = (PicHandle) scrapHandle;
 target = (**thePict).picFrame;
 picWidth = target.right - target.left;
 picHeight = target.bottom - target.top;

 /*Tile the offscreen image with copies of the PICT. */
 clipCopies = 0;
 target.top = 0;
 target.bottom = picHeight;
 while (target.top < thePort->portRect.bottom)
 { target.left = 0;
 target.right = picWidth;
 while (target.left < thePort->portRect.right)
 { DrawPicture (thePict, & target);
 OffsetRect (& target, picWidth, 0);
 ++clipCopies;
 }
 OffsetRect (& target, 0, picHeight);
 }
 }
 else /* no PICT? */
 FillRect (& thePort->portRect, black); 
 /* paint it black, you devil */
 SetPortBits (& winBits);

 /*Bring in ALL this to show the speed of a nearly-full-screen dissolve. 
*/
 dissolve (& offScreen, & theWindow.port.portBits,
 & thePort->portRect, & thePort->portRect, 10);
 Delay (60L, & dummy);
 EraseRect (& thePort->portRect);

 /*If we have a clipboard PICT, dissolve these things in at
 varying speeds -- note the last parameter to dissolve(). */
 if (scrapResult >= 0)
 { clipCount = 1;
 target.top = 0;
 target.bottom = picHeight;
 while (target.top < thePort->portRect.bottom)
 { target.left = 0;
 target.right = picWidth;
 while (target.left < thePort->portRect.right)
 { /* The first image dissolves in 20 steps; the last
 one in fewer. */
 dissolve (&offScreen, &theWindow.port.portBits, &target, &target, 20 
* (clipCopies - clipCount) / clipCopies);
 OffsetRect (& target, picWidth, 0);
 ++clipCount;
 }
 OffsetRect (& target, 0, picHeight);
 }
 Delay (60L, & dummy);
 EraseRect (& thePort->portRect);
 }

 /*Let ’em draw selections to be dissolved in. */
 SetWTitle (& theWindow, “\pClick and drag to dissolve   press a key 
to exit”);
 do
 { GetNextEvent (everyEvent, & evt);
 if (evt.what == mouseDown)
 { GlobalToLocal (& evt.where);
 if (PtInRect (evt.where, & thePort->portRect))
 { Point startPt, endPt, curPt;
 Rect frame;

 PenPat (gray); PenSize (1, 1); PenMode (patXor);
 startPt = evt.where;
 endPt = evt.where;
 Pt2Rect (startPt, endPt, & frame);
 FrameRect (& frame);
 while (StillDown ())
 { GetMouse (& curPt);
 if (curPt.v < 0) curPt.v = 0; 
 /* hack to avoid mysterious bugs */
 if (! EqualPt (curPt, endPt))
 { FrameRect (& frame);
 endPt = curPt;
 Pt2Rect (startPt, endPt, & frame);
 FrameRect (& frame);
 }
 }
 FrameRect (& frame);
 dissolve (& offScreen, & theWindow.port.portBits,
 & frame, & frame, 20);
 }
 else SysBeep (2); /* click outside window */
 } /* end of handling mousedown */
 } while (evt.what != keyDown);
} /* end of main () */

/* dissolve -- quick driver for dissMask routines.  It sets up the dissolve 
and does it in specified number of iterations. */
static void dissolve (srcBits, dstBits, srcRect, dstRect, steps)
 BitMap *srcBits, *dstBits;
 Rect *srcRect, *dstRect;
 unsigned short steps;
{dissMaskInfo info;
 unsigned long pixPerStep;

/* Initialize for dissolve; if it fails, just copy outright: */
 if (! dissMaskInit (srcRect, & info))
 { CopyBits (srcBits, dstBits, srcRect, dstRect, srcCopy, 0L);
 return;
 }

 if (steps == 0) steps = 1;
 pixPerStep = (info.pixLeft / steps) + 1;

 /*Main dissolve loop: repeatedly darken the mask
 bitmap and CopyMask through it. */
 HideCursor ();
 while (info.pixLeft)
 { dissMaskNext (& info, pixPerStep);
 CopyMask (srcBits, & info.maskMap, dstBits,
 srcRect, & info.maskRect, dstRect);
 }
 /*Clean up: */
 ShowCursor ();
 dissMaskFinish (& info);
} /* end of dissolve () */

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Final Cut Pro 10.6.4 - Professional vide...
Redesigned from the ground up, Final Cut Pro combines revolutionary video editing with a powerful media organization and incredible performance to let you create at the speed of thought.... Read more
iMovie 10.3.4 - Edit personal videos and...
With a streamlined design and intuitive editing features, iMovie lets you create Hollywood-style trailers and beautiful movies like never before. Browse your video library, share favorite moments,... Read more
Motion 5.6.2 - Create and customize Fina...
Motion is designed for video editors, Motion 5 lets you customize Final Cut Pro titles, transitions, and effects. Or create your own dazzling animations in 2D or 3D space, with real-time feedback as... Read more
iMazing 2.15.8 - Complete iOS device man...
iMazing is the world’s favourite iOS device manager for Mac and PC. Millions of users every year leverage its powerful capabilities to make the most of their personal or business iPhone and iPad.... Read more
VueScan 9.7.90 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
Compressor 4.6.2 - Adds power and flexib...
Compressor adds power and flexibility to Final Cut Pro X export. Customize output settings, work faster with distributed encoding, and tap into a comprehensive set of delivery features. Features:... Read more
Capture One 15.3.2.11 - RAW workflow sof...
Capture One is a professional RAW converter offering you ultimate image quality with accurate colors and incredible detail from more than 400 high-end cameras - straight out of the box. It offers... Read more
Vivaldi 5.4.2753.28 - An advanced browse...
Vivaldi is a browser for our friends. We live in our browsers. Choose one that has the features you need, a style that fits and values you can stand by. From the look and feel, to how you interact... Read more
Parallels Desktop 18.0.0 - Run Windows a...
Parallels allows you to run Windows and Mac applications side by side. Choose your view to make Windows invisible while still using its applications, or keep the familiar Windows background and... Read more
TechTool Pro 16.0.1 - Hard drive and sys...
TechTool Pro has long been one of the foremost utilities for keeping your Mac running smoothly and efficiently. With the release of this version, it has become more proficient than ever. Main... Read more

Latest Forum Discussions

See All

Turn-Based RPG ‘Avatar: Generations’ Sof...
Square Enix London Mobile, Navigator Games, and Paramount Consumer Products just announced that the turn-based RPG Avatar: Generations based on Nickelodeon’s Avatar: The Last Airbender is soft launching this month for mobile. Avatar: Generations is... | Read more »
Tower of Fantasy launches today and brin...
Level Infinite and Hotta Studio have announced the release of their very ambitious looking shared open world MMORPG Tower of Fantasy. With its cross-platform functionality between PC and mobile, it looks to be one to roll the dice on and enjoy at... | Read more »
‘Genshin Impact’ Version 3.0 Gets a New...
After HoYoverse released Genshin Impact (Free) version 2.8 on all platforms, the company has slowly been teasing the major upcoming 3.0 update. This update features the Sumeru region with many characters. While details on the update including a... | Read more »
Out Now: ‘Tower of Fantasy’, ‘Tightrope...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »
SwitchArcade Round-Up: ‘Book Quest’, ‘Cl...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for August 10th, 2022. In today’s article, we’ve got a little news about an update to a game I really like, a few new releases to summarize, and some sales to look at. A bit of a quiet... | Read more »
‘Pine Tar Poker’ is an Otherworldly Poke...
Developer BJ Malicoat, who put out the well-received and former Apple Game of the Day pick Downwordly in June of last year, is back working on another mobile game project called Pine Tar Poker, and it has caught my attention. Why? Because it’s a... | Read more »
Darkness Rises celebrates four years of...
Four years of uptime for a mobile game is akin to eternity, and this is exactly the milestone that Darkness Rises has reached. It is important for developers to keep updating to keep the game fresh, and NEXON has announced a massive anniversary... | Read more »
Keep Your Smatphone’s Case On When Using...
The original Gamevice was born as a sort of offshoot of the weird Wikipad gaming tablet/controller/hybrid thing way back in 2014. Interestingly, the first Gamevice controller for iOS only supported the iPad mini and launched in 2015, with versions... | Read more »
SwitchArcade Round-Up: A ‘Splatoon 3’ Ni...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for August 9th, 2022. In today’s article, we’ve got some news about a Splatoon 3 Nintendo Direct, a review of QUByte’s Thunderbolt Collection, a single new release summary, and the usual... | Read more »
Orangepixel’s Pacifist Survival Game ‘Re...
Back in June we learned that long-time mobile developer Orangepixel, who also makes games for PC and consoles (including the Atari VCS!), would be bringing the unique survival game Residual to mobile devices sometime this year. Originally launched... | Read more »

Price Scanner via MacPrices.net

Apple has 24-inch M1 iMacs available starting...
Apple has 24-inch M1 iMacs with M1 CPUs (8-core CPU/7-core GPU) available today in their Certified Refurbished store for $1099 shipped. Their price is $200 off standard MSRP. Each iMac is in like-new... Read more
13″ M1 MacBook Airs in stock today for $799,...
QuickShip Electronics has open-box return 13″ M1 MacBook Airs in stock and on sale for $200 off MSRP on their eBay store right now, each with free express delivery. According to QuickShip, “The item... Read more
In stock today: Mac Studio models for up to $...
Apple retailer Expercom has Mac Studio models in stock today and on sale for up to $400 off Apple’s MSRP, depending on configuration. Their prices are the lowest price available for a Mac Studio from... Read more
Mac mini with M1 CPU and 512GB of storage on...
Amazon has the M1 Mac mini with a 512GB SSD in stock today on sale for $749.99 including free shipping. Their price is $150 off Apple’s MSRP, and it’s the lowest price available for this... Read more
Need a Mac or iPad for school? Get a free App...
Apple’s Back to School promotion for 2022 continues to run through September 26, 2022. As part of this promotion, Apple will include a free $150 Apple Gift Card with the purchase of any MacBook Air,... Read more
Apple Watch SE on sale for $50 off MSRP
Amazon has Apple Watch SE GPS models on sale for $50 off MSRP for a limited time, each including free shipping. Their prices are the lowest currently available for SE Watches: – 40mm Apple Watch SE... Read more
Save $310 on a 14″ 24-core GPU M1 Max MacBook...
Save $310 on 14″ MacBook Pros with 24-core M1 Max processors at Apple (32GB RAM/1TB SSD) with these Certified Refurbished models in stock today for $2789 in Space Gray or Silver colors. Regular price... Read more
14″ M1 Pro MacBook Pros available today at Ap...
Apple has Certified Refurbished standard-configuration 14″ MacBook Pros with M1 Pro CPUs available today for up to $250 off original MSRP, starting at $1799. Each model features a new outer case,... Read more
13″ MacBook Air with M2 CPU, in Starlight, on...
Apple retailer Expercom has the new Starlight 13″ MacBook Air with an M2 CPU (8GB RAM/256GB SSD) on sale for $1135.05, shipped, through August 12, 2022. Their price is $64 off Apple’s MSRP, and it’s... Read more
14″ M1 Pro MacBook Pro with 1TB SSD on sale f...
Expercom is offering a $200 instant discount on the 14″ M1 Pro MacBook Pro with a 1TB SSD through August 12, 2022. Their discount reduces the price of this configuration to $1999 shipped — the lowest... Read more

Jobs Board

Solutions Engineering Manager - *Apple* - S...
…in our Hardware and Advanced Solutions group leading and developing our Apple technical practice to increase revenue and profitability. The ideal candidate would Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Sephora Beauty Advisor - *Apple* Blossom Ma...
Sephora Beauty Advisor - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.