[comp.sys.mac.programmer] Drawing Analog Clocks

jackiw@cs.swarthmore.edu (Nick Jackiw) (05/02/90)

rimola@flamingo.metaphor.com (Carlos Rimola) writes:
> There are two parts to these problem: 1) In the Mac environment, how do
> you preserve the previous contents of an arbitrary window area when you
> want to temporarily draw over it (are regions the only option?) and 2)
> What clever algorithms are there for implementing an analog clock.
> 

1) The easiest way is to use patXor mode when drawing your hands. 
Drawing the same thing twice with patXor will leave your background the
way it was before drawing anything.  (Draw once to display, once to 'erase'.)
Unfortunately, this is also the cheesiest method, in that patXor won't
draw your hands as solid black, but rather as some odd intersection of
each hand with its neighbors and with the background.


> BTW, for question 1, I know about off-screen bitmaps but have found
> them to be inefficient when the number of on-screen windows that need
> to be updates is high (e.g. 10 clock windows showing different times).
> 

I find this hard to believe.  Short of writing directly to the screen
memory, nothing will be faster than copyBits if used correctly. If your
bitmapped backgrounds exist offscreen, bit-aligned, at the appropriate
color depth, and registered to unclipped areas of your windows, you can
easily redraw the entire screen in under a second.  The number of
windows on the screen is moot--the number of pixels per screen is fixed.
I posted code on bitmap alignment a while back; there are also technotes
discussing technique.

For the smoothest action possible, I'd recommend

for each clock
	one static bitmap of the clock background
	one scratch bitmap
	your window

All three should be aligned and at the same depth.  Each time you need to
reorient your hands, copy the background into the scratch bitmap, draw the
hands (patCopy) into the scratch bitmap, and then copy the whole thing to
the screen.  Precomputing the endpoints of the hands, rather than using
trigonometric functions while redrawing, will make things considerably
more speedy.


> Please respond via mail if possible..


--
------------------------
Nick Jackiw		jackiw@cs.swarthmore.edu  "Every minute of the future
Visual Geometry Project	jackiw@swarthmr.bitnet     is a memory of the past."
Swarthmore College, PA 19081-1397				-Laivach

rimola@flamingo.metaphor.com (Carlos Rimola) (05/03/90)

In article <1123@metaphor.Metaphor.COM> rimola@flamingo.metaphor.com (Carlos Rimola) writes:
>I am writing an analog clock program that draws hour, minute 
>and second hands.  I would like to be able to draw each of these hands
>only when it changes.  The problem is, when I draw, for example, a new
>second hand I will want to erase the previous one but if it overlayed
>the hours and minutes I will be erasing parts of those.
>
>There are two parts to these problem: 1) In the Mac environment, how do
>you preserve the previous contents of an arbitrary window area when you
>want to temporarily draw over it (are regions the only option?) and 2)
>What clever algorithms are there for implementing an analog clock.
>
>BTW, for question 1, I know about off-screen bitmaps but have found
>them to be inefficient when the number of on-screen windows that need
>to be updates is high (e.g. 10 clock windows showing different times).
>

Thanks to all who replied with info and imaginative suggestions.  There are
too many people to reply to so I have decided to post another msg, specially
since it appears to be a subject of wide interest.  I have also included
some of the replies I received at the end of this article, they may be
of help to someone with a similar problem.

First, I guess I should have explained that I do have a working program but
I am not satisfied with either the visual effect or performance when the
number of clocks is > 2.  Oh, BTW, the time zone for each clock is diferrent
so the hands are at diferent positions. Also, each clock can be on a
diferrent size window.

I have tried two techniques:

1) Drawing each clock in sucession to a single off-screen bitmap and then
CopyBits-ing to each of the on-screen clock windows.  This, as pointed out
by Scott Sutherland, has the disadvantage that visually you can see the
clocks changing in sucession and with a high number of clocks, more than 1
second can elapse before the last clock(s) are updated (it's slow).

2) The second technique I have tried is to draw each hand every second. With
a couple of clocks on the screen this techniques has a flickering effect.
The preferred way would be to only re-draw those parts of the clock which 
have changed, this would be mainly the second hands.  The problem, as 
previously explained, is that the old hand (for the prev second) needs to be
erased and if it overlayed another hand then that hand or a part of it
will also get erased.
(See Scott's suggestion below for possibly improving this one.)

Some people have suggested using an XOR drawing mode but this will erase
other hands if they coincide or "look funny" if a diferrent pen pattern
is used for each hand.

A thought that comes to mind as far as having to save then restore a part
of the screen is that this is a function which the pointer needs to perform.
Does anyone know what technique it uses?  of course in that case, there is
only 1  pointer and it is always a 16x16 (tiny) area so CopyBits probably
does the trick.

****************************************************************************

To close, here are some of the additional suggestions received.  I will
likely adopt Scott's sugestion or a variant of it.


From: apple!Apple.COM!lsr (Larry Rosenstein)
Organization: Objects-R-Us,  Apple Computer, Inc.

The answer to #1 depends on how you want the clock to look.  I wrote a 
clock program and I made the second hand use XOR mode for drawing, which 
allows it to erase it self easily.  For drawing the other hands, I used 
copy mode and redrew all the hands every minute.  

The disadvantage of XOR mode is that when if the second hand crosses 
another hand then it will look a bit funny.  The only way to avoid that 
problem is to use an offscreen bitmap.  You would composite the desired 
image offscreen and use CopyBits to draw it.  This will let you draw the 
clock without any flashing.  I don't think offscreen bitmaps will be any 
slower, regardless of the number of clocks you are displaying.

As for clever algorithms, the only thing I did in my program was to 
precompute the position of the clock hands and store the coordinates in a 
resource.  That way I didn't need to do any sines or cosines while the 
program was running.

Larry Rosenstein, Apple Computer, Inc.
Object Specialist

From: Scott Sutherland <sas@cis.ohio-state.edu>
Organization: Ohio State University Department of Dance

The way I see it, you have several options.  You could still use an offscreen 
bitmap.  You simply use one, instead of one for each clock.  The only 
problem with this is that the updates to your 10 clocks will be a visible 
progression from the first to the last.  Drawing each in turn offscreen and 
blitting them to the screen will be the slowest progression, but you may
be able to speed it up somewhat by using a few tricks.  Are you displaying
various time zones?  Ie, are the minute hands all in the same position?
If this is true, then you can draw the clock face and the minute hand 
offscreen just once, and then blit it to each window, adding the hour hand
once it is there.  If both are black, then you won't be able to tell which 
is on top.

If you decide to stay away from offscreen bitmaps, then I would do one of two 
things:

The most simple solution is to have a plain clock face (filled with the 
backgroud pattern of the window) and use two plain lines for the hands.  
Perhaps a short, thick one for the hour and a longer, thinner one for the
minutes.  These will draw REALLY fast, so you should be able to wait for
th  TickCount to change (to give you the most time before the screen is 
refreshed) and then draw whichever one is to be changed, first draw it in the 
background pat (to erase it), then draw the hand and the minute hand.   
Do all your calculations before waiting for the TickCount to change, so you
are only doing something like this:

{Calculate all points here}
{below, I use the points minute and hour to denote the lengths of the minute 
hour hands broken up into their vertical and horizontal componants}

{change the pen pattern to the background pattern}
PenPat(white);

{move the pen location to the center of the dial}
MoveTo(center.h,center.v)

{wait for the next tick to eliminate flicker}
saveTick := TickCount;
Repeat 
Until TickCount > saveTick;

{ok, the tick just changed, so draw REALLY FAST}
{use Move and Draw as opposed to MoveTo and DrawTo}
Line(minute.h,minute.v);  {erase minute hand}

PenPat(black);	{change the pat to black}
Move(minute.h,minute.v);  {move back to center}
Line(hour.h,hour.v);	{draw the hour hand}
Move(hour,h.hour.v);	{move back to center}
Line(miute.h,minute.v);	{draw the minute hand}


If the above method does not meet your needs, let me know, and I'll help you
work out a method using a mask region.  Regions are a bit of a pain though
and they S L O W down everything a great deal, so avoid them where you can.

Let me know how it goes.

Cheers,
---
Scott Sutherland	sas@cis.ohio-state.edu
Staff Software Developer
The Ohio State University, Department of Dance

From: zben@umd5.UMD.EDU (Ben Cranston)

The usual way this is done is using "xor" drawing mode, and drawing the object
twice (the second time is to erase it).  The problem here is that when your
second hand overlays your minute hand or your minute hand overlays your hour
hand then both hands will disapper (as the draw of the OTHER hand ends up
erasing the FIRST hand).  Does this make any sense to you?

The only other idea I could give you is to temporarily save what is "under"
the hand when you draw it.  For simplicity consider the hand as a simple line
segment.  The two end points define a rectangle.  Before drawing, do a
CopyBits of the screen area to an offscreen bitmap, then draw the hand.
After the time has gone by, CopyBits the offscreen bitmap back to the screen,
thus erasing the draw of the hand.  Note that the dimensions of the offscreen
bitmap will in general change every time you do this (because the hand is,
after all, moving) but you don't have to keep allocating and freeing memory,
just use a block for the offscreen bitmap that is big enough for the maximun
size copy (I think this will be the square one, when the hand is at 45 degrees)
and just dont us it all every time.  You will have to adjust the PortRect of
the offscreen bitmap and probably the RowBytes as well.

Think through how this will work, because you might be doing the above
algorithm three times (once for each hand) and you don't want them to 
interact.  Maybe you can do only two saves, and special case the hour hand,
since you will probably have a routine to draw the virgin clock face hanging
around anyway...

Best of luck!

jherr@umbio.med.miami.edu (Jack Herrington) (05/03/90)

In article <1126@metaphor.Metaphor.COM> rimola@flamingo.metaphor.com (Carlos Rimola) writes:
>In article <1123@metaphor.Metaphor.COM> rimola@flamingo.metaphor.com (Carlos Rimola) writes:
>>There are two parts to these problem: 1) In the Mac environment, how do
>>you preserve the previous contents of an arbitrary window area when you
>>want to temporarily draw over it (are regions the only option?) and 2)
>>What clever algorithms are there for implementing an analog clock.

Draw the minutes and the hours as triangular regions, then union them
into one region.  Then paint that in XOR.  You can't use alternating patterns.
You could then do the seconds as a single XORed line.  This will avoid offscreen
BMAPs.  And the most regions you would have would be 4.

Something like this:

{
	RgnHandle totalHands,hourHands,minuteHands,lastClock;

	totalHands = NewRgn();
	hourHands = NewRgn();
	minuteHands = NewRgn();
	lastClock = NewRgn();

	PenMode(patXor);
	PenPat(white);

	while(1)
	{
		if ( minutes or hours has changed )
		{
			OpenRgn();
			{ Draw the hours }
			CloseRgn(hourHands);

			OpenRng();
			{ Draw the mintues }
			CloseRgn(minuteHands);

			UnionRgn(hourHands,minuteHands,totalHands);

			PaintRgn(lastClock);
			PaintRgn(totalHands);

			CopyRgn(totalHands,lastClock);
		}

		{ Draw the seconds }
	}

	DisposeRgn(totalHands);
	DisposeRgn(hourHands);
	DisposeRgn(minuteHands);
	DisposeRgn(lastClock);
}

Damn, somebody should pay me for this. :-)
--
"Aaaah, you gonna take me home tonight... Ahh, you gonna let it hang out,
 fat bottom girls, you make the rockin' world go 'round..."
								-Queen