void GraphicsContext::rotate(float angle)
{
    if (paintingDisabled())
        return;
    CGContextRotateCTM(platformContext(), angle);
    m_data->rotate(angle);
}
Exemple #2
0
static double 	Quartz_StrWidth(char *str, 
				R_GE_gcontext *gc,
				NewDevDesc *dd)
{
    QuartzDesc *xd = (QuartzDesc*)dd->deviceSpecific;
    CGPoint position;

    CGContextSaveGState( GetContext(xd) );
    CGContextTranslateCTM( GetContext(xd), 0, 0 );

    CGContextScaleCTM( GetContext(xd), -1, 1);

    CGContextRotateCTM( GetContext(xd), -1.0 * 3.1416);

    CGContextSetTextDrawingMode( GetContext(xd), kCGTextInvisible );

    Quartz_SetFont(gc->fontfamily, gc->fontface, gc->cex,  gc->ps, dd);

    CGContextShowTextAtPoint( GetContext(xd), 0, 0, str, strlen(str) );

    position = CGContextGetTextPosition( GetContext(xd) );

    CGContextRestoreGState( GetContext(xd) );
    return(position.x);
}
void GraphicsContext::rotate(float angle)
{
    if (paintingDisabled())
        return;
    CGContextRotateCTM(platformContext(), angle);
    m_data->rotate(angle);
    m_data->m_userToDeviceTransformKnownToBeIdentity = false;
}
Exemple #4
0
//-----------------------------------------------------------------------------
static void addOvalToPath (CGContextRef c, CPoint center, CGFloat a, CGFloat b, CGFloat start_angle, CGFloat end_angle)
{
	CGContextSaveGState (c);
	CGContextTranslateCTM (c, center.x, center.y);
	CGContextScaleCTM (c, a, b);
	CGContextRotateCTM (c, radians (-90.f));

	CGContextMoveToPoint (c, cos (radians (start_angle)), sin (radians (start_angle)));

	CGContextAddArc(c, 0, 0, 1, radians (start_angle), radians (end_angle), 1);

	CGContextRestoreGState(c);
}
DISABLED_DRAW_TEST_F(CGContext, ShadowWithRotatedCTM, WhiteBackgroundTest) {
    CGContextRef context = GetDrawingContext();
    CGRect bounds = GetDrawingBounds();

    CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
    CGContextSetLineWidth(context, 5);

    CGContextSetShadow(context, CGSize{ 10.f, 10.f }, 1.0);

    CGPoint center = _CGRectGetCenter(bounds);
    CGRect rect = _CGRectCenteredOnPoint({ 150, 150 }, center);
    CGPoint rectCenter = _CGRectGetCenter(rect);

    CGContextTranslateCTM(context, rectCenter.x, rectCenter.y);
    CGContextRotateCTM(context, 15.f * M_PI / 180.f);
    CGContextTranslateCTM(context, -rectCenter.x, -rectCenter.y);

    CGContextStrokeRect(context, rect);
}
/* Draw the source PDF document into the context and then draw the stamp PDF document
on top of it. When drawing the stamp on top, place it along the diagonal from the lower
left corner to the upper right corner and center its media rect to the center of that
diagonal. */
void StampWithPDFDocument(CGContextRef context, 
			CGPDFDocumentRef sourcePDFDoc, 
			CGPDFDocumentRef stampFileDoc, CGRect stampMediaRect)
{
    CGRect pageRect;
    float angle;
    size_t i, numPages = CGPDFDocumentGetNumberOfPages(sourcePDFDoc);
    
    // Loop over document pages and stamp each one appropriately.
    for(i = 1 ; i <= numPages ; i++)
    {
		// Use the page rectangle of each page from the source to compute
		// the destination media box for each page and the location of
		// the stamp.
		CGRect pageRect = CGPDFDocumentGetMediaBox(sourcePDFDoc, i);
		CGContextBeginPage(context, &pageRect);
		CGContextSaveGState(context);
			// Clip to the media box of the page.
			CGContextClipToRect(context, pageRect);	    
			// First draw the content of the source document.
			CGContextDrawPDFDocument(context, pageRect, sourcePDFDoc, i);
			// Translate to center of destination rect, that is the center of 
			// the media box of content to draw on top of.
			CGContextTranslateCTM(context, 
				pageRect.size.width/2, pageRect.size.height/2);
			// Compute angle of the diagonal across the destination page.
			angle = atan(pageRect.size.height/pageRect.size.width);
			// Rotate by an amount so that drawn content goes along a diagonal
			// axis across the page.
			CGContextRotateCTM(context, angle);
			// Move the origin so that the media box of the PDF to stamp
			// is centered around center point of destination.
			CGContextTranslateCTM(context, 
			-stampMediaRect.size.width/2, 
			-stampMediaRect.size.height/2);
			// Now draw the document to stamp with on top of original content.
			CGContextDrawPDFDocument(context, stampMediaRect, 
					stampFileDoc, 1);
		CGContextRestoreGState(context);
		CGContextEndPage(context);
    }
}
void PDFDocumentImage::adjustCTM(GraphicsContext* context) const
{
    // rotate the crop box and calculate bounding box
    float sina = sinf(-m_rotation);
    float cosa = cosf(-m_rotation);
    float width = m_cropBox.width();
    float height = m_cropBox.height();

    // calculate rotated x and y edges of the corp box. if they're negative, it means part of the image has
    // been rotated outside of the bounds and we need to shift over the image so it lies inside the bounds again
    CGPoint rx = CGPointMake(width * cosa, width * sina);
    CGPoint ry = CGPointMake(-height * sina, height * cosa);

    // adjust so we are at the crop box origin
    const CGFloat zero = 0;
    CGContextTranslateCTM(context->platformContext(), floorf(-min(zero, min(rx.x, ry.x))), floorf(-min(zero, min(rx.y, ry.y))));

    // rotate -ve to remove rotation
    CGContextRotateCTM(context->platformContext(), -m_rotation);

    // shift so we are completely within media box
    CGContextTranslateCTM(context->platformContext(), m_mediaBox.x() - m_cropBox.x(), m_mediaBox.y() - m_cropBox.y());
}
static void quartzgen_begin_page(GVJ_t *job)
{
	CGRect bounds = CGRectMake(0.0, 0.0, job->width, job->height);
	
	if (!job->context) {
		
		switch (job->device.id) {
		
		case FORMAT_PDF:
			{
				/* create the auxiliary info for PDF content, author and title */
				CFStringRef auxiliaryKeys[] = {
					kCGPDFContextCreator,
					kCGPDFContextTitle
				};
				CFStringRef auxiliaryValues[] = {
					CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s %s"), job->common->info[0], job->common->info[1]),
					job->obj->type == ROOTGRAPH_OBJTYPE ?
						CFStringCreateWithBytesNoCopy(kCFAllocatorDefault, (const UInt8 *)job->obj->u.g->name, strlen(job->obj->u.g->name), kCFStringEncodingUTF8, false, kCFAllocatorNull)
						: CFSTR("")
				};
				CFDictionaryRef auxiliaryInfo = CFDictionaryCreate(
					kCFAllocatorDefault,
					(const void **)&auxiliaryKeys,
					(const void **)&auxiliaryValues,
					sizeof(auxiliaryValues)/sizeof(auxiliaryValues[0]),
					&kCFTypeDictionaryKeyCallBacks,
					&kCFTypeDictionaryValueCallBacks
				);
				
				/* create a PDF for drawing into */
				CGDataConsumerRef data_consumer = CGDataConsumerCreate(job, &device_data_consumer_callbacks);
				job->context = CGPDFContextCreate(data_consumer, &bounds, auxiliaryInfo);
				
				/* clean up */
				CGDataConsumerRelease(data_consumer);
				CFRelease(auxiliaryInfo);
				int i;
				for (i = 0; i < sizeof(auxiliaryValues)/sizeof(auxiliaryValues[0]); ++i)
					CFRelease(auxiliaryValues[i]);
			}
			break;
		
		default: /* bitmap formats */
			{	
				size_t bytes_per_row = (job->width * BYTES_PER_PIXEL + BYTE_ALIGN) & ~BYTE_ALIGN;
				
				void* buffer = NULL;
				
#if __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 20000
				
				/* iPhoneOS has no swap files for memory, so if we're short of memory we need to make our own temp scratch file to back it */
				
				size_t buffer_size = job->height * bytes_per_row;
				mach_msg_type_number_t vm_info_size = HOST_VM_INFO_COUNT;
				vm_statistics_data_t vm_info;
				
				if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&vm_info, &vm_info_size) != KERN_SUCCESS
					|| buffer_size * 2 > vm_info.free_count * vm_page_size)
				{
					FILE* temp_file = tmpfile();
					if (temp_file)
					{
						int temp_file_descriptor = fileno(temp_file);
						if (temp_file_descriptor >= 0 && ftruncate(temp_file_descriptor, buffer_size) == 0)
						{
							buffer = mmap(
								NULL,
								buffer_size,
								PROT_READ | PROT_WRITE,
								MAP_FILE | MAP_SHARED,
								temp_file_descriptor,
								0);
							if (buffer == (void*)-1)
								buffer = NULL;
						}
						fclose(temp_file);
					}
				}
				if (!buffer)
					buffer = mmap(
						NULL,
						buffer_size,
						PROT_READ | PROT_WRITE,
						MAP_ANON| MAP_SHARED,
						-1,
						0);				
#endif				
				
				/* create a true color bitmap for drawing into */
				CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
				job->context = CGBitmapContextCreate(
					buffer,							/* data: MacOSX lets system allocate, iPhoneOS use manual memory mapping */
					job->width,						/* width in pixels */
					job->height,					/* height in pixels */
					BITS_PER_COMPONENT,				/* bits per component */
					bytes_per_row,					/* bytes per row: align to 16 byte boundary */
					color_space,					/* color space: device RGB */
					kCGImageAlphaPremultipliedFirst	/* bitmap info: premul ARGB has best support in OS X */
				);
				job->imagedata = CGBitmapContextGetData((CGContextRef)job->context);
				
				/* clean up */
				CGColorSpaceRelease(color_space);
			}
			break;
		}
		
	}
	
	/* start the page (if this is a paged context) and graphics state */
	CGContextRef context = (CGContextRef)job->context;
	CGContextBeginPage(context, &bounds);
	CGContextSaveGState(context);
	CGContextSetMiterLimit(context, 1.0);
	CGContextSetLineJoin(context, kCGLineJoinRound);
	
	/* set up the context transformation */
	CGContextScaleCTM(context, job->scale.x, job->scale.y);
	CGContextRotateCTM(context, -job->rotation * M_PI / 180.0);
	CGContextTranslateCTM(context, job->translation.x, job->translation.y);
}
void doClippedEllipse(CGContextRef context)
{
    CGPoint theCenterPoint = { 120., 120. };
    CGSize theEllipseSize = { 100., 200. };
    float dash[1] = { 2 };
    static CGColorRef opaqueBrownColor = NULL, opaqueOrangeColor = NULL;
    // Initialize the CGColorRefs if necessary.
    if(opaqueBrownColor == NULL){
		// The initial value of the color array is an 
		// opaque brown in an RGB color space.
		float color[4] = { 0.325, 0.208, 0.157, 1.0 };
		CGColorSpaceRef theColorSpace = getTheCalibratedRGBColorSpace();
		// Create a CGColorRef for opaque brown.
		opaqueBrownColor = CGColorCreate(theColorSpace, color);
		// Make the color array correspond to an opaque orange.
		color[0] = 0.965 ; color[1] = 0.584; color[2] = 0.059;
		// Create another CGColorRef for opaque orange.
		opaqueOrangeColor = CGColorCreate(theColorSpace, color);
	}
	// Draw two ellipses centered about the same point, one
	// rotated 45 degrees from the other.
	CGContextSaveGState(context);
		// Ellipse 1
		createEllipsePath(context, theCenterPoint, theEllipseSize);
		CGContextSetFillColorWithColor(context, opaqueBrownColor);
		CGContextFillPath(context);
		// Translate and rotate about the center point of the ellipse.
		CGContextTranslateCTM(context, theCenterPoint.x, theCenterPoint.y);
		// Rotate by 45 degrees.
		CGContextRotateCTM(context, DEGREES_TO_RADIANS(45));
		// Ellipse 2
		// CGPointZero is a pre-defined Quartz point corresponding to 
		// the coordinate (0,0).
		createEllipsePath(context, CGPointZero, theEllipseSize);
		CGContextSetFillColorWithColor(context, opaqueOrangeColor);
		CGContextFillPath(context);
	CGContextRestoreGState(context);
	
	CGContextTranslateCTM(context, 170., 0.);
	// Now use the first ellipse as a clipping area prior to
	// painting the second ellipse.
	CGContextSaveGState(context);
		// Ellipse 3
		createEllipsePath(context, theCenterPoint, theEllipseSize);
		CGContextSetStrokeColorWithColor(context, opaqueBrownColor);
		CGContextSetLineDash(context, 0, dash, 1);
		// Stroke the path with a dash.
		CGContextStrokePath(context);
		// Ellipse 4
		createEllipsePath(context, theCenterPoint, theEllipseSize);
		// Clip to the elliptical path.
		CGContextClip(context);
		CGContextTranslateCTM(context, theCenterPoint.x, theCenterPoint.y);
		// Rotate by 45 degrees.
		CGContextRotateCTM(context, DEGREES_TO_RADIANS(45));
		// Ellipse 5
		createEllipsePath(context, CGPointZero, theEllipseSize);
		CGContextSetFillColorWithColor(context, opaqueOrangeColor);
		CGContextFillPath(context);
	CGContextRestoreGState(context);
}
/*
OvalTeenDrawEventHandler : Handles the draw events for the "Ovalteen" window.

Parameter DescriptionsinHandler : A reference to the current handler call chain. This is passed to your handler so that you can call CallNextEventHandler if you need to.
inEvent : The event that triggered this call.
inUserData : The application-specific data you passed in to InstallEventHandler.
*/
OSStatus OvalTeenDrawEventHandler (EventHandlerCallRef inHandler, EventRef inEvent, void* inUserData) {
	OSStatus status = eventNotHandledErr;
	CGContextRef context;
	CGRect bounds;
	double a, b;
	int count, k;
	
	//CallNextEventHandler in order to make sure the default handling of the inEvent 
	// (drawing the white background) happens
	status = CallNextEventHandler( inHandler, inEvent );
	require_noerr(status, CantCallNextEventHandler);
	
	// Get the CGContextRef
	status = GetEventParameter (inEvent, 
								kEventParamCGContextRef, 
								typeCGContextRef, 
								NULL, 
								sizeof (CGContextRef),
								NULL,
								&context);
	require_noerr(status, CantGetEventParameter);
	
	// Get the bounding rectangle
	status = HIViewGetBounds ((HIViewRef) inUserData, &bounds);
	require_noerr(status, CantGetViewBounds);

	// Calculate the dimensions for an oval inside the bounding box
	a = 0.9 * bounds.size.width/4;
	b = 0.3 * bounds.size.height/2;
	count = 5;
	
	// Set the fill color to a partially transparent blue
	CGContextSetRGBFillColor(context, 0, 0, 1, 0.5);

	// Set the stroke color to an opaque black
	CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);

	// Set the line width to be used, in user space units.
	CGContextSetLineWidth(context, 3);

	// Save the conexts state because we are going to be moving the origin and
	// rotating context for drawing, but we would like to restore the current
	// state before drawing the next image.
	CGContextSaveGState(context);
	
	// Move the origin to the middle of the first image (left side) to draw.
	CGContextTranslateCTM(context, bounds.size.width/4, bounds.size.height/2);
	
	// Draw "count" ovals, rotating the context around the newly translated origin
	// 1/count radians after drawing each oval
	for (k = 0; k < count; k++)
		 {
		 // Paint the oval with the fill color
		paintOval(context, CGRectMake(-a, -b, 2 * a, 2 * b));

		// Frame the oval with the stroke color
		frameOval(context, CGRectMake(-a, -b, 2 * a, 2 * b));

		// Rotate the context around the center of the image
		CGContextRotateCTM(context, pi / count);
		 }
	// Restore the saved state to a known state for dawing the next image
	CGContextRestoreGState(context);

	// Calculate a bounding box for the rounded rect
	a = 0.9 * bounds.size.width/4;
	b = 0.3 * bounds.size.height/2;
	count = 5;
	
	// Set the fill color to a partially transparent red
	CGContextSetRGBFillColor(context, 1, 0, 0, 0.5);

	// Set the stroke color to an opaque black
	CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);

	// Set the line width to be used, in user space units.
	CGContextSetLineWidth(context, 3);

	// Save the conexts state because we are going to be moving the origin and
	// rotating context for drawing, but we would like to restore the current
	// state before drawing the next image.
	CGContextSaveGState(context);
	
	// Move the origin to the middle of the second image (right side) to draw.
	CGContextTranslateCTM(context, bounds.size.width/4 + bounds.size.width/2, bounds.size.height/2);

	for (k = 0; k < count; k++)
		 {
		 // Fill then stroke the rounding rect, otherwise the fill would cover the stroke
		fillRoundedRect(context, CGRectMake(-a, -b, 2 * a, 2 * b), 20, 20);
		strokeRoundedRect(context, CGRectMake(-a, -b, 2 * a, 2 * b), 20, 20);
		// Rotate the context for the next rounded rect
		CGContextRotateCTM(context, pi / count);
		 }
	CGContextRestoreGState(context);

CantCallNextEventHandler:
CantGetEventParameter:
CantGetViewBounds:
		
		return status;
}
Exemple #11
0
static void 	Quartz_MetricInfo(int c, 
				  R_GE_gcontext *gc,
				  double* ascent, double* descent, 
				  double* width,
				  NewDevDesc *dd)
{
    FMetricRec myFMetric;
    QuartzDesc *xd = (QuartzDesc *) dd-> deviceSpecific;
    char testo[2];
	char *ff;
    CGrafPtr savedPort;
    Rect bounds;
    CGPoint position;
	unsigned char tmp;

    testo[0] = c;
    testo[1] = '\0';
/*    fprintf(stderr,"c=%c,>%s<\n",c,testo);
  */  GetPort(&savedPort);

    SetPort(GetWindowPort(xd->window));

    Quartz_SetFont(gc->fontfamily, gc->fontface, gc->cex,  gc->ps, dd);

    if(c==0){
        FontMetrics(&myFMetric);
        *ascent = xd->yscale *floor(gc->cex * gc->ps + 0.5) * FixedToFloat(myFMetric.ascent);
        *descent = xd->yscale*floor(gc->cex * gc->ps + 0.5) * FixedToFloat(myFMetric.descent);
    } else {

    CGContextSaveGState( GetContext(xd) );
    CGContextTranslateCTM( GetContext(xd), 0, 0 );
    CGContextScaleCTM( GetContext(xd), -1, 1);
    CGContextRotateCTM( GetContext(xd), -1.0 * 3.1416);
    CGContextSetTextDrawingMode( GetContext(xd), kCGTextInvisible );

	Quartz_SetFont(gc->fontfamily, gc->fontface, gc->cex,  gc->ps, dd);

	ff = Quartz_TranslateFontFamily(gc->fontfamily, gc->fontface, xd->family);
	tmp = (unsigned char)c;
    if( (gc->fontface == 5) || (strcmp(ff,"Symbol")==0)){
       if( (tmp>31) && IsThisASymbol(tmp))
        testo[0] = (char)Lat2Uni[tmp-31-1];
       else	
		Quartz_SetFont(gc->fontfamily, -1, gc->cex,  gc->ps, dd);
	 } else {
        if(tmp>127)
         testo[0] = (char)Lat2Mac[tmp-127-1];
     }	 

    CGContextShowTextAtPoint( GetContext(xd), 0, 0, testo, 1 );
    
	
    position = CGContextGetTextPosition( GetContext(xd) );
    CGContextRestoreGState( GetContext(xd) );
    
        QDTextBounds(1,testo,&bounds);
        *ascent = -bounds.top;
        *descent = bounds.bottom;
        *width = bounds.right - bounds.left;
        *width = position.x;
    }    
    
    SetPort(savedPort);
/*    fprintf(stderr,"ascent=%f, descent=%f,width=%f\n",*ascent, *descent, *width);
*/
 return;
}
Exemple #12
0
static void 	Quartz_Text(double x, double y, char *str,
			    double rot, double hadj, 
			    R_GE_gcontext *gc,
			    NewDevDesc *dd)
{
    int len,i;
    char *buf=NULL;
	char *ff;
	char symbuf;
    unsigned char tmp;
    QuartzDesc *xd = (QuartzDesc*)dd->deviceSpecific;
	 
    CGContextSaveGState( GetContext(xd) );
    CGContextTranslateCTM( GetContext(xd), x, y );

    CGContextScaleCTM( GetContext(xd) , -1, 1);

    CGContextRotateCTM( GetContext(xd) , (-1.0  + 2*rot/360)  * 3.1416);

    Quartz_SetStroke( gc->col, gc->gamma, dd);

    CGContextSetTextDrawingMode( GetContext(xd), kCGTextFill );
    Quartz_SetFill(gc->col, gc->gamma, dd);

	Quartz_SetFont(gc->fontfamily, gc->fontface, gc->cex,  gc->ps, dd);
    len = strlen(str);
	ff = Quartz_TranslateFontFamily(gc->fontfamily, gc->fontface, xd->family);

    if( ((gc->fontface == 5) || (strcmp(ff,"Symbol")==0)) && (len==1) ){
	   tmp = (unsigned char)str[0];
       if(tmp>31)
        symbuf = (char)Lat2Uni[tmp-31-1];
	   else
	    symbuf = str[0];
       if( !IsThisASymbol(tmp) ){
		 Quartz_SetFont(gc->fontfamily, -1, gc->cex,  gc->ps, dd);
		 symbuf = str[0];
       }
	 if(WeAreOnPanther) 
      CGContextShowTextAtPoint( GetContext(xd), 0, 0, &symbuf, len );
	 else
	  CGContextShowTextAtPoint( GetContext(xd), 0, 0, str, len );
     } else {
     if( (buf = malloc(len)) != NULL){

      if( strcmp(ff,"Symbol")==0){
		for(i=0;i <len;i++){
			tmp = (unsigned char)str[i];
			if(tmp>31)
				buf[i] = (char)Lat2Uni[tmp-31-1];
			else
				buf[i] = str[i];
		}
	  } else {
		for(i=0;i <len;i++){
			tmp = (unsigned char)str[i];
			if(tmp>127)
				buf[i] = (char)Lat2Mac[tmp-127-1];
			else
				buf[i] = str[i]; 
		}
	 }
	 CGContextShowTextAtPoint( GetContext(xd), 0, 0, buf, len );
     free(buf);
     }  
    }
    CGContextRestoreGState( GetContext(xd) );
}
Exemple #13
0
void MyTimerProc ( EventLoopTimerRef inTimer, void *inUserData )
{
    WindowRef window = (WindowRef)inUserData;
    
    static int i = 0;
    static int j = 0;
    static int step = 5;
    GrafPtr curPort;
    CGContextRef context;
    CGrafPtr windowPort = GetWindowPort( window );
        
    GetPort(&curPort);
    SetPort(windowPort);
    
    CreateCGContextForPort( windowPort, &context );
    
    //	Do our drawing in here
    CGContextSetGrayFillColor( context, 0.0, 1.0 );
    CGContextFillRect( context, CGRectMake( 0, 0, 800, 600 ) );

    CGContextSetGrayFillColor( context, 1.0, 1.0 );
    CGContextSetRGBStrokeColor( context, 1.0, 0.0, 0.0, 1.0 );
	CGContextSetLineWidth( context, 5.0 );
    CGContextSetLineCap( context, kCGLineCapRound );
    CGContextSetLineJoin( context, kCGLineJoinRound );
    CGContextSetMiterLimit( context, 5 );

    
    //	Draw each of the 4 transform demos
    //
    //	1.  Translate - move the origin, draw the graphic
    
    //	Place the origin in the llh corner of the upper left quadrant
    CGContextSaveGState( context );
    CGContextTranslateCTM( context, i / 2, i / 2 + 300 );
    CGContextRotateCTM( context, -2.0 * 3.1416 / 8 );
    drawGraphic( context, 0, 0 );
    CGContextRestoreGState( context );


    //	2.  Rotate - move origin, do rotation, draw the graphic
    
    //	Put the origin back into the llh corner of the upper right quadrant
    CGContextSaveGState( context );
    
    if ( j < 1000/2 )
    {
        //	Rotate around left circle
        CGContextTranslateCTM( context, 500, 450 );
        CGContextRotateCTM( context, -j * 3.1416 * 2.0 / 500.0 - 3.1416 );
        CGContextTranslateCTM( context, -100, -20 );
        drawGraphic( context, 0, 0 );
    }
    if ( j >= 1000/2 )
    {
        //	Rotate under bottom half and over top right 1/4
        CGContextTranslateCTM( context, 700, 450 );
        CGContextRotateCTM( context, j * 3.1416 * 2.0 / 500.0 );
        CGContextScaleCTM( context, 1, 1 );
        CGContextTranslateCTM( context, -100, 20 );
        CGContextRotateCTM( context, 3.1416 );
        drawGraphic( context, 0, 0 );
    }

    CGContextRestoreGState( context );
    

    //  3.  Scale - move origin, set scale, draw the graphic
    CGContextSaveGState( context );
    CGContextScaleCTM( context, i * 400.0 / 500.0 / 40.0, i * 300.0 / 500.0 / 40.0 );
    CGContextRotateCTM( context, -3.1416 / 4 );
    drawGraphic( context, 0, 0 );
    CGContextRestoreGState( context );
    

    //  4.  Show the basic graphic
    CGContextTranslateCTM( context, 600, 150 );
    drawGraphic( context, 0, 0 );
    
    CGContextFlush( context );
    CGContextRelease( context );
    
    SetPort ( curPort );
    
    i += step;
    j += abs( step );
    
    if ( i >= 500 )
    {
        step = -step;
    }
    
    if ( i <= 0 )
    {
        step = -step;
        j = 0;
    }
}