[net.sources.mac] StringArt source for the Mac

hitchens@ut-sally.UUCP (Ron Hitchens, Vogon poetry critic for the Times) (05/31/85)

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by ut-sally!hitchens on Fri May 31 02:58:21 CDT 1985
# Contents:  README stringart.c gennums.c
 
echo x - README
sed 's/^@//' > "README" <<'@//E*O*F README//'
  Here's the source to Stringart which I promised a few weeks ago.  I've been
swamped doing real work and just haven't gotten around to it until now.
I received quite a few requests for this so I'm posting it rather than sending
individual copies to people.  I spent quite a bit of time adding profuse
comments so that MacBeginners can see what's necessary for a minimal
application in C on the Mac.
  This code is functionally the same as the object code I posted a few weeks
ago, except that a few things have been robustified and it will now use the
entire screen if running on a Lisa.  If anyone wants this new version to run
on a Lisa but doesn't have MegaMax to compile it with I'll be happy to send
them a binhex'd copy.  To help relieve net congestion I won't repost the 
object code.
  This code was prepared with Megamax C version 2.  As far as I know there is
no reason why it shouldn't work with version 1 also, but I haven't tried it.
  There are two programs here.  One generates a third C source file which
declares the tables of numbers used to draw the vectors.  This file and the
stringart program itself are linked together to produce the application.
Compilation instructions are in the comments in the stringart.c source.

  Feel free to distribute this thing far and wide.  Perhaps someone could
post it on CompuServe.  I'd be glad to hear any comments about StringArt.

Ron Hitchens	hitchens@ut-sally.ARPA	...!ihnp4!ut-sally!hitchens.UUCP
@//E*O*F README//
chmod u=rw,g=r,o=r README
 
echo x - stringart.c
sed 's/^@//' > "stringart.c" <<'@//E*O*F stringart.c//'
/*
 *	StringArt	May 1985    Ron Hitchens
 *
 *	hitchens@ut-sally.ARPA	ihnp4!ut-sally!hitchens.UUCP
 *
 *	This program was adapted from a demo for the Sun Microsystems
 *	Unix workstation
 *	Ported to the Mac May 10, 1985 by Ron Hitchens
 *
 *	It runs a lot faster on the Sun, even though it's also 68000 based
 *	and is running multituser Unix 4.2BSD, has more layers of software
 *	between you and the screen, and is computing coordinates using
 *	floating point.  It's clock rate is faster, and it's vector drawing
 *	routines are coded for 1-bit-wide lines, but even so...
 *
 *	This program is public domain.  It may be be freely distributed,
 *	copied and used.  All I ask is that my name and this notice remain
 *	on it.  And that modifications be noted in this source listing and
 *	returned to me if possible at the above email address.
 *
 *	History:
 *		May 85	Hitchens	Created.
 *		May 28	Hitchens	Look at screen size vars, add comments
 *
 *
 *	Compilation instructions with Megamax C:
 *	You need the C source files stringart.c and gennums.c
 *		1. compile and link gennums.c
 *		2. run gennums, it creates a C source file named stringnums.c
 *		   This name is hard-coded in gennums.c
 *		3. Compile stringnums.c, but don't link it.  Keep stringnums.o
 *		4. Compile stringart.c
 *		5. Link stringart.o and stringnums.o together to make stringart
 *		6. Run StringArt.
 *		7. Sit back and enjoy the show.
 *	If you make changes to stringart, after doing the above, you can skip
 *	to 4.  Stringnums doesn't need to be re-compiled.
 *	If you want to port this to another flavor of C you're on your own.
 *
 *	The icon was created manually with the resource editor, to retain
 *	the icon when recompiling just specify the name of the old 
 *	StringArt application file as the output of the linker.  It will
 *	overwrite the code but won't disturb the icon resources.
 */

/*
	This demo creates random vector designs.  This is accomplished by
	randomly choosing a function for each coordinate halve of the two
	points describing a vector that moves through two dimensional
	space. Both x coordinate halves cannot be the same since the design
	would simply be a collection of vertical lines. Similarly both
	y coordinate halves cannot be the same.

	The functions are:

	function[0][x] = sin( 2*PI*x/NUMLINES )
	function[1][x] = -sin( 2*PI*x/NUMLINES )
	function[2][x] = cos( 2*PI*x/NUMLINES ) 
	function[3][x] = -cos( 2*PI*x/NUMLINES ) 
	function[4][x] = sin( 4*PI*x/NUMLINES )
	function[5][x] = -sin( 4*PI*x/NUMLINES )
	function[6][x] = cos( 4*PI*x/NUMLINES ) 
	function[7][x] = -cos( 4*PI*x/NUMLINES ) 
	function[8][x] = sin( 6*PI*x/NUMLINES )
	function[9][x] = -sin( 6*PI*x/NUMLINES )
	function[10][x] = cos( 6*PI*x/NUMLINES ) 
	function[11][x] = -cos( 6*PI*x/NUMLINES ) 
	function[12][x] = 2 * abs( x - (NUMLINES/2) - 1 )	*not used*

	The values of the functions were pre-computed to have the demo
	run as fast as possible.  The program runs in an endless loop, it 
	will terminate if the "Quit" menu item is selected or a 'Q' is typed.
*/

#include <qd.h>  	/* quikdraw */
#include <qdvars.h>	/* quickdraw globals */
#include <win.h>	/* window manager */
#include <menu.h>	/* menu manager */
#include <event.h>	/* event manager */

#define NUMLINES	343	/* number of vectors in a design */
#define NUM_FUNCTIONS	12	/* number of functions */
#define VOFFSET		19	/* offset from top, drag is under menu bar */
#define BORDER		2	/* surrounding blank border */
				
/* get real size of screen, in case of Lisa or something new */
#define HSIZE (qdvars.screenbits.bounds.a.right)
#define VSIZE (qdvars.screenbits.bounds.a.bottom)

windowrecord	myrecord;	/* if you can't figure out what these are */
windowptr	mywindow;	/* you'd better give up right now */
menuhandle	mymenu;

int holding = 0;		/* are we currently holding? */
int step = 0;			/* single step? */

int q [4][NUMLINES];		/* temp array for scaled coordinates */
extern int p [NUM_FUNCTIONS][NUMLINES];
	/* pre-computed points, scaled by 1024 */

main()
{
	int i, j, k, l, m;
	long secs;
	
	init ();		/* do housekeeping */
	showinfo ();		/* display bragging info */
	getdatetime (&qdvars.randseed);	/* randomize the random() function */

	while (1) {		/* main loop, go round and round forever */
		/* pick the functions */
		while ((i = random() % NUM_FUNCTIONS) < 0) /* nothing */ ;
		while ((j = random() % NUM_FUNCTIONS) == i || (j < 0));
		while ((k = random() % NUM_FUNCTIONS) < 0);
		while ((l = random() % NUM_FUNCTIONS) == k || (l < 0));

		/* scalepoints was a hack to speed up the line drawing, by
		   avoiding the scaling computations for each coordinate
		   while the lines are being drawn.  It didn't help as much
		   as I'd hoped, only about a second is saved, maybe less.
		   Apparently I'm pushing QuickDraw as fast as it will go.
		   The same calculations using floating point took about
		   45 seconds, YOW! */
		     
		scalepoints (i, k, j, l);	/* scale to the window */
		while (holding && !step)	/* loop here if holding */
			checkmenu ();		/* watch events when holding */

		blankwindow (mywindow);		/* clear to black or white */
		setport (mywindow);		/* point QD at my window */
			/* the above statement shouldn't be necessary, but
			   something sneaky, like the screensaver DA, could
			   be lurking in the darkness and may change the
			   grafport out from under us.  Grrr... */
		obscurecursor ();		/* get rid of the cursor */

		for (m = 0; m < NUMLINES; m++) {	/* draw the vectors */
			moveto (q [0][m], q [1][m]);
			lineto (q [2][m], q [3][m]);
		}

		systemtask ();			/* in case of DAs */
		if (!holding) sleep (6);	/* pause for ~6 secs */
		step = 0;			/* only good for one shot */
	}
}


/* the numbers in the array p were pre-computed using floating point
   arithmetic, they were all sin or cos functions and so yielded
   numbers [-1, 1].  These numbers were then scaled up by 1024 so they
   could be stored as integers.  1024 was used so they could be scaled down
   again with a shift-right-10 rather than a divide operation.  I use two
   statements to compute each number to be sure the intermediate values
   are calculated as longs, the values involved far exceed the magnitude
   possible in a 16-bit int.  */
     
scalepoints (x1, y1, x2, y2)	int x1, y1, x2, y2;
{
	long  l, h, v;
	int i;
	
	h = (HSIZE) / 2;
	v = (VSIZE - VOFFSET) / 2;
	for (i = 0; i < NUMLINES; i++) {
		l = (((long)p [x1][i] * (h - BORDER)) >> 10) + h;
		q [0][i] = (int)l;
		l = (((long)p [y1][i] * (v - BORDER)) >> 10) + v;
		q [1][i] = (int)l;
		l = (((long)p [x2][i] * (h - BORDER)) >> 10) + h;
		q [2][i] = (int)l;
		l = (((long)p [y2][i] * (v - BORDER)) >> 10) + v;
		q [3][i] = (int)l;
		checkmenu ();	/* see if there are any events to be handled */
	}
}


checkmenu ()
	/* test for and handle any events, such as keyboard and mouse */
{
	eventrecord	theevent;
	windowptr	tmpptr;
	
			/* loop as long as there are events in the queue */
	while (getnextevent (everyevent, &theevent)) {
		switch (theevent.what) {
		 case abortevt:		/* something is telling us to stop */
		 	exit (0);
		 case keydown:		/* got a key */
					/* does this key belong to a menu? */
			if (menukey ((char) (theevent.message % 256))) {
				/* yes */
				do_menu (menukey ((char) (theevent.message % 256)));
			} else {
		 		step = 1;	/* step once (undoc feature) */	
		 		return;
			}
		 	break;
		 case mousedown:		/* mouse clicked */
			if (findwindow(&theevent.where, &tmpptr) == inmenubar){
				do_menu (menuselect (&theevent.where));
			} else {
				step = 1;	/* cycle once */
			}
			break;
		 case updateevt:	/* always get one of these at startup */
		 	beginupdate (mywindow);
			endupdate (mywindow);	/* only way to get rid of it */
			break;
		 case nullevent:	/* shouldn't happen, filtered above */
		 	return;
		 default:
			break;		/* not interested in anything else */
		}
	}
}

do_menu (item)		long item;	/* handle a menu selection */
{
	hilitemenu (1);
	switch (item) {
	 case 0x00010001:		/* menu 1, item 1 (hold) */
		toggle_hold ();
		 break;
	 case 0x00010002:		/* menu 1, item 2 (invert) */
		invert ();
		break;
	 case 0x00010003:		/* menu 1, item 3 (author info) */
		showinfo ();
		break;
	 case 0x00010004:		/* menu1, item 4 (quit) */
		exit (0);
	}
	hilitemenu (0);
}
	

toggle_hold ()
{
	holding = !holding;		/* toggle the flag */
	checkitem (mymenu, 1,  holding);
		/* place or remove checkmark, depending on holding */
}

invert ()
{
	setport (mywindow);			/* on the off chance... */
	if (mywindow->pnmode == patbic) {
		penmode (patcopy);			/* draw black */
		textmode (srccopy);
	} else {
		penmode (patbic);			/* draw white */
		textmode (srcbic);
	}
	invertrect (&mywindow->portrect);	/* flip what's onscreen */
}
			 

init ()					/* do initial housekeeping chores */
{
	managerinits ();
	windowinits ();
	menuinits ();
}

managerinits()
{
   	initgraf(&theport);		/* initialize qd (theport in qdvars) */
	initfonts();			/* initialize font manager */
	initwindows();			/* initialize window manager */
	initmenus();			/* initialize menus */
	initcursor();			/* set cursor to arrow */
	flushevents(everyevent, 0);	/* lose any outstanding events */
}

windowinits()
{
	rect		temprect;
	
	setrect(&temprect, 0, VOFFSET, HSIZE, VSIZE);
	mywindow = newwindow(&myrecord, &temprect, "Stringart", 1, 0,
				(long) -1, 0, (long) 0);
	setport(mywindow);		/* draw in my window */
	selectwindow(mywindow);		/* make mine active */
	pennormal ();			/* make sure pen has defaults */
	penmode (patbic);		/* make it draw white */
	textmode (srcbic);		/* make text draw white */
	textfont (0);			/* use system font */
	textface ((short) bold);	/* use a fancy text style */
}

menuinits ()
{
	mymenu = newmenu (1, "StringArt");	/* allocate a menu struct */
	appendmenu (mymenu,  "Hold/H");		/* load the item values */
	appendmenu (mymenu, "Invert/I");
	appendmenu (mymenu, "Author Info/A");
	appendmenu (mymenu, "Quit/Q");
	insertmenu (mymenu, 0);			/* let the OS know about it */
	drawmenubar ();				/* make it show up */
}

sleep (s)	int s;
	/* sleep for approx s seconds, could vary from s-1 to s seconds */
{
	long s1, s2;

	getdatetime (&s1);		/* what time is it now? */
	s1 += s;			/* looking for now + s seconds */
	while (1) {
		getdatetime (&s2);
		if (s1 <= s2) break;	/* ok, the time has come */
		checkmenu ();		/* keep an eye on events around you */
	}
}

blankwindow (w)		windowptr w;
{
	if (w->pnmode == patbic) {	/* drawing white? */
		fillrect (&w->portrect, qdvars.black); /* yes, paint window black */
	} else {
		fillrect (&w->portrect, qdvars.white); /* no, paint it white */
	}
}

showinfo ()		/* display bragging info */
{
	static char *s [] = {
		"StringArt by Ron Hitchens",
		"hitchens@ut-sally.ARPA  or  ...!ihnp4!ut-sally!hitchens.UUCP",
		"", "Written in MegaMax C",
		"This program is public domain", "",
		"While in Hold, click mouse to single step",
		"To save an image, type:  SHIFT/CMD/3" ,
		"To print an image, type:  SHIFT/CMD/4" };
	int i, n;
	static int busy = 0;
	
	hilitemenu (1);			/* turn on the menu */
	if (busy) return;		/* info screen already up */
	busy = 1;			/* to prevent recursive displays */
	setport (mywindow);		/* just in case */
	obscurecursor ();		/* get rid of the cursor */
	blankwindow (mywindow);
	n = sizeof (s) / sizeof (s [0]);	/* how many lines of text? */
	textsize (24);				/* get fancy for first line */
	for (i = 0; i < n; i++) {		/* display all the text */
		moveto ((HSIZE / 2) - (stringwidth (s [i]) / 2),
			(VSIZE / (n + 1)) + (i * (VSIZE / (n + 1))));
		drawstring (s [i]);	/* draw one of the text lines */
		textsize (12);		/* back to normal for the rest */
	}
	if (!holding) sleep (10);		/* wait a bit */
	hilitemenu (0);				/* turn off the menu */
	busy = 0;				/* ok, I'm finished */
}
@//E*O*F stringart.c//
chmod u=rw,g=r,o=r stringart.c
 
echo x - gennums.c
sed 's/^@//' > "gennums.c" <<'@//E*O*F gennums.c//'
/*	gennums.c	generate number tables for stringart	*/

#include <stdio.h>		/* standard I/O */
#include <fmath.h>		/* floating point math */

/*
	This demo creates random vector designs.  This is accomplished by
	randomly choosing a function for each coordinate halve of the two
	points describing a vector that moves through two dimensional
	space. Both x coordinate halves cannot be the same since the design
	would simply be a collection of vertical lines. Similarly both
	y coordinate halves cannot be the same.

	The functions are:

	function[0][x] = sin( 2*PI*x/NUMLINES )
	function[1][x] = -sin( 2*PI*x/NUMLINES )
	function[2][x] = cos( 2*PI*x/NUMLINES ) 
	function[3][x] = -cos( 2*PI*x/NUMLINES ) 
	function[4][x] = sin( 4*PI*x/NUMLINES )
	function[5][x] = -sin( 4*PI*x/NUMLINES )
	function[6][x] = cos( 4*PI*x/NUMLINES ) 
	function[7][x] = -cos( 4*PI*x/NUMLINES ) 
	function[8][x] = sin( 6*PI*x/NUMLINES )
	function[9][x] = -sin( 6*PI*x/NUMLINES )
	function[10][x] = cos( 6*PI*x/NUMLINES ) 
	function[11][x] = -cos( 6*PI*x/NUMLINES ) 
	function[12][x] = 2 * abs( x - (NUMLINES/2) - 1 )

	The values of the functions were pre computed to have the demo
	run as fast as possible.  The program will only terminate on 
	interrupt since it is in an endless loop.
*/

/* these should be in a common header file, but I'm lazy */
#define NUMLINES	343		/* number of vectors in a design */
#define NUM_FUNCTIONS	12		/* number of functions */

int p [NUMLINES];

main()
{
	int i, j, k, l, m;
	FILE *f;

	f = fopen ("stringnums.c", "w");
	fprintf (f, "\n\t/* This source file generated by gennums.c */\n\n\n");
	fprintf (f, "#define NUM_FUNCTIONS %d\n", NUM_FUNCTIONS);
	fprintf (f, "#define NUMLINES %d\n\n", NUMLINES);
	fprintf (f, "int p [NUM_FUNCTIONS][NUMLINES] = {\n\n");
	for (i = 0;  i < NUM_FUNCTIONS;  i++) {
		cifer (i, 1024);
			/* scaling down is faster with a power of 2 */
		dumparray (f, i);
	}
	fprintf (f, "\t}\n};\n\n");
	fclose (f);
}

dumparray (f, n)	FILE *f;	int n;
{
	int i;
	
	if (n != 0)
		fprintf (f, "\t},\n\n");
	fprintf (f, "\t{\n");
	for (i = 0; i < NUMLINES; i++) {
		fprintf (f, "\t%d", p[i]);
		if (i != NUMLINES - 1)
			fprintf (f, ",");
		if (i % 7 == 6)
			fprintf (f, "\n");
	}
}
		

#define PI 3.1415927

int
cifer (func, scale)	int func, scale;	/* Hey Uncle Jed... */
{
	float tmp;
	int x;
	
	for (x = 0; x < NUMLINES; x++) {
			/* clumsy, but doesn't need to be efficient */
		switch (func) {
		 case 0:
			tmp = sin( 2*PI*x/NUMLINES);
			break;
		 case 1:
			tmp = -sin( 2*PI*x/NUMLINES);
			break;
		 case 2:
			tmp = cos( 2*PI*x/NUMLINES);
			break;
		 case 3:
			tmp = -cos( 2*PI*x/NUMLINES);
			break;
		 case 4:
			tmp = sin( 4*PI*x/NUMLINES);
			break;
		 case 5:
			tmp = -sin( 4*PI*x/NUMLINES);
			break;
		 case 6:
			tmp = cos( 4*PI*x/NUMLINES);
			break;
		 case 7:
			tmp = -cos( 4*PI*x/NUMLINES);
			break;
		 case 8:
			tmp = sin( 6*PI*x/NUMLINES);
			break;
		 case 9:
			tmp = -sin( 6*PI*x/NUMLINES);
			break;
		 case 10:
			tmp = cos( 6*PI*x/NUMLINES);
			break;
		 case 11:
			tmp = -cos( 6*PI*x/NUMLINES);
			break;
		 case 12:
			tmp = 2 * abs( x - (NUMLINES/2) - 1); /* not used */
			break;
		 default:
		 	tmp = x;	/* shouldn't happen */
			break;
		}
		p [x] = (int)(tmp * scale);
	}
}

			 

@//E*O*F gennums.c//
chmod u=rw,g=r,o=r gennums.c
 
exit 0