<Error>: doClip: empty path.
written to your console, with no obvious place to put a breakpoint. To find out where you're doing something wrong, put a breakpoint on CGPostError
.float domain[2] = { 0.0, 1.0 }; // 1-in function float range[8] = { 0.0, 1.0, // N-out, RGBA 0.0, 1.0, 0.0, 1.0, 0.0, 1.0 }; CGFunctionCallbacks callbacks = { 0, evaluate, NULL }; CGFunctionRef shaderFunction; shaderFunction = CGFunctionCreate (self, // info / rock / context 1, // # of inputs for domain domain, // domain 4, // # of inputs for range range, // range &callbacks); CGColorSpaceRef deviceRGB; deviceRGB = CGColorSpaceCreateDeviceRGB (); CGShadingRef shader; shader = CGShadingCreateAxial (deviceRGB, // colorspace cgpoint(start), // start of axis cgpoint(end), // end of axis shaderFunction, // shader, 1-n, n-out NO, // extend start NO); // extend end CGContextSaveGState (context); { NSRect bounds = [self bounds]; CGContextClipToRect (context, cgrect(bounds)); CGContextDrawShading (context, shader); } CGContextRestoreGState (context); [self drawSpotAt: start size: 4]; [self drawSpotAt: end size: 4]; CGFunctionRelease (shaderFunction); CGColorSpaceRelease (deviceRGB); CGShadingRelease (shader);And the evaluator function is given an array of inputs and outputs. Use the in value(s) (which run from your domain's start-to-finish values) to generate the out values (which should be in the range's start-to-finish values):
static void evaluate (void *info, const float *in, float *out) { float thing; thing = in[0]; out[0] = thing; out[1] = thing; out[2] = thing; out[3] = 1.0; } // evaluate
CGPathRef border = ... get a path from somewhere; CGContextRef context = UIGraphicsGetCurrentContext (); CGContextSaveGState (context); { CGContextAddPath (context, border); CGContextClip (context); // draw draw draw } CGContextRestoreGState (context);
// Based on Foley and van Dam algorithm. void ConvertHSLToRGB (const CGFloat *hslComponents, CGFloat *rgbComponents) { CGFloat hue = hslComponents[0]; CGFloat saturation = hslComponents[1]; CGFloat lightness = hslComponents[2]; CGFloat temp1, temp2; CGFloat rgb[3]; // "temp3" if (saturation == 0) { // Like totally gray man. rgb[0] = rgb[1] = rgb[2] = lightness; } else { if (lightness < 0.5) temp2 = lightness * (1.0 + saturation); else temp2 = (lightness + saturation) - (lightness * saturation); temp1 = (lightness * 2.0) - temp2; // Convert hue to 0..1 hue /= 360.0; // Use the rgb array as workspace for our "temp3"s rgb[0] = hue + (1.0 / 3.0); rgb[1] = hue; rgb[2] = hue - (1.0 / 3.0); // Magic for (int i = 0; i < 3; i++) { if (rgb[i] < 0.0) rgb[i] += 1.0; else if (rgb[i] > 1.0) rgb[i] -= 1.0; if (6.0 * rgb[i] < 1.0) rgb[i] = temp1 + ((temp2 - temp1) * 6.0 * rgb[i]); else if (2.0 * rgb[i] < 1.0) rgb[i] = temp2; else if (3.0 * rgb[i] < 2.0) rgb[i] = temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - rgb[i]) * 6.0); else rgb[i] = temp1; } } // Clamp to 0..1 and put into the return pile. for (int i = 0; i < 3; i++) { rgbComponents[i] = MAX (0.0, MIN (1.0, rgb[i])); } } // ConvertHSLToRGB
// Based on Foley and van Dam algorithm. void ConvertRGBToHSL (const CGFloat *rgbComponents, CGFloat *hslComponents) { CGFloat red = rgbComponents[0]; CGFloat green = rgbComponents[1]; CGFloat blue = rgbComponents[2]; CGFloat maxColor = MAX (red, MAX (green, blue)); CGFloat minColor = MIN (red, MIN (green, blue)); CGFloat deltaColor = maxColor - minColor; CGFloat hue, saturation; CGFloat lightness = (maxColor + minColor) / 2.0; // All the same means a gray color. if (maxColor == minColor) { saturation = 0.0; hue = 0.0; // officially undefined with gray, but go ahead and zero out. } else { if (lightness < 0.5) saturation = deltaColor / (maxColor + minColor); else saturation = deltaColor / (2.0 - deltaColor); if (red == maxColor) hue = (green - blue) / deltaColor; else if (green == maxColor) hue = 2.0 + (blue - red) / deltaColor; else hue = 4.0 + (red - green) / deltaColor; // H will be in the range of 0..6, convert to degrees. hue *= 60.0; // Convert to positive angle. if (hue < 0) hue += 360.0; } // Clamp to legal values. hslComponents[0] = MAX (0.0, MIN (360.0, hue)); hslComponents[1] = MAX (0.0, MIN (1.0, saturation)); hslComponents[2] = MAX (0.0, MIN (1.0, lightness)); } // ConvertRGBToHSL
CGAffineTransformRotate
. I personally think better in degrees (360 degrees in a circle).
Convert degrees to radians by multiplying by 180 / pi
.
Convert radians to degrees by multiplying by pi / 180
I have a couple of #defines I stick into a common header for projects that need it;
#define BWDegToRad(d) ((d) * M_PI / 180.0) #define BWRadToDeg(r) ((r) * 180 / M_PI)
- (CGImageRef) newMaskFromImage: (UIImage *) image { CGImageRef maskRef = image.CGImage; CGImageRef mask2 = CGImageMaskCreate (CGImageGetWidth (maskRef), CGImageGetHeight (maskRef), CGImageGetBitsPerComponent (maskRef), CGImageGetBitsPerPixel (maskRef), CGImageGetBytesPerRow (maskRef), CGImageGetDataProvider (maskRef), NULL, NO); // provider, shouldInterpolate return mask2; } // newMaskFromImage
NSString *blah = @"Placeholder Pane"; NSSize size = [blah sizeWithAttributes: nil]; NSPoint startPoint; startPoint.x = bounds.origin.x + bounds.size.width / 2 - size.width / 2; startPoint.y = bounds.origin.y + bounds.size.height / 2 - size.height / 2; [blah drawAtPoint: startPoint withAttributes: nil];
- (void) drawGradientInRect: (CGRect) rect colorSpace: (CGColorSpaceRef) colorSpcae context: (CGContextRef) context { CGFloat startComponents[4] = { 254.0 / 255.0, 254.0 / 255.0, 254.0 / 255.0, 1.0 }; CGFloat endComponents[4] = { 206.0 / 255.0, 206.0 / 255.0, 206.0 / 255.0, 1.0 }; CGColorRef startColor = CGColorCreate (colorSpace, startComponents); CGColorRef endColor = CGColorCreate (colorSpace, endComponents); NSArray *array = [NSArray arrayWithObjects: (id)startColor, (id)endColor, nil]; CGGradientRef gradient = CGGradientCreateWithColors (colorSpace, (CFArrayRef)array, NULL); CGPoint endPoint = rect.origin; endPoint.y += rect.size.height; // Don't let the gradient bleed all over everything CGContextSaveGState (context); { CGContextClipToRect (context, rect); CGContextDrawLinearGradient (context, gradient, rect.origin, endPoint, 0); } CGContextRestoreGState (context); CGGradientRelease (gradient); CGColorRelease (startColor); CGColorRelease (endColor); } // drawGradientInRect
- (void) drawText: (NSString *) text centeredInRect: (CGRect) rect font: (UIFont *) font { CGSize textSize = [text sizeWithFont: font constrainedToSize: rect.size lineBreakMode: NSLineBreakByWordWrapping]; // Center text rect inside of |rect|. CGRect textRect = CGRectMake (CGRectGetMidX(rect) - textSize.width / 2.0, CGRectGetMidY(rect) - textSize.height / 2.0, textSize.width, textSize.height); [text drawInRect: textRect withFont: font lineBreakMode: NSLineBreakByWordWrapping alignment: NSTextAlignmentCenter]; } // drawText
- (void) drawRect: (CGRect) rect { NSString *string = @"132"; CGContextRef ctx = UIGraphicsGetCurrentContext (); CGContextTranslateCTM (ctx, 0.0, self.bounds.size.height); CGContextScaleCTM (ctx, 1.0, -1.0); CGContextSelectFont (ctx, "Helvetica-Bold", 48.0, kCGEncodingMacRoman); CGContextSetTextDrawingMode (ctx, kCGTextFillStroke); // Draw a background to see the text on. CGContextSetFillColorWithColor (ctx, [[UIColor yellowColor] CGColor]); CGContextFillRect (ctx, self.bounds); CGContextSetFillColorWithColor (ctx, [[UIColor blackColor] CGColor]); CGContextSetStrokeColorWithColor (ctx, [[UIColor whiteColor] CGColor]); CGContextSetLineWidth (ctx, 2); CGContextSetShadow (ctx, CGSizeMake (3.0, 3.0), 2.5); CGContextShowTextAtPoint (ctx, 50, 50, [string UTF8String], string.length); } // drawRect(Thanks to Chris Liscio for handy pointers)
#define cgrect(nsrect) (*(CGRect *)&(nsrect)) - (void) drawRect: (NSRect) rect { NSRect bounds = [self bounds]; NSGraphicsContext *cocoaContext = [NSGraphicsContext currentContext]; CGContextRef context = (CGContextRef)[cocoaContext graphicsPort]; CGContextSetLineWidth (context, 5.0); CGContextBeginPath(context); { CGContextAddRect (context, cgrect(bounds)); CGContextSetRGBFillColor (context, 1.0, 0.9, 0.8, 1.0); } CGContextFillPath(context); } // drawRect
- (NSImage *) captureScreenImageWithFrame: (NSRect) frame { // Fetch a graphics port of the screen CGrafPtr screenPort = CreateNewPort (); Rect screenRect; GetPortBounds (screenPort, &screenRect); // Make a temporary window as a receptacle NSWindow *grabWindow = [[NSWindow alloc] initWithContentRect: frame styleMask: NSBorderlessWindowMask backing: NSBackingStoreRetained defer: NO screen: nil]; CGrafPtr windowPort = GetWindowPort ([grabWindow windowRef]); Rect windowRect; GetPortBounds (windowPort, &windowRect); SetPort (windowPort); // Copy the screen to the temporary window CopyBits (GetPortBitMapForCopyBits(screenPort), GetPortBitMapForCopyBits(windowPort), &screenRect, &windowRect, srcCopy, NULL); // Get the contents of the temporary window into an NSImage NSView *grabContentView = [grabWindow contentView]; [grabContentView lockFocus]; NSBitmapImageRep *screenRep; screenRep = [[NSBitmapImageRep alloc] initWithFocusedViewRect: frame]; [grabContentView unlockFocus]; NSImage *screenImage = [[NSImage alloc] initWithSize: frame.size]; [screenImage addRepresentation: screenRep]; // Clean up [grabWindow close]; DisposePort(screenPort); return (screenImage); } // captureScreenImageWithFrame
- (void) fillMask: (CGImageRef) mask withColor: (UIColor *) color atOffset: (CGPoint) offset { if (color == nil) color = [UIColor purpleColor]; // everybody loves purple CGContextRef context = UIGraphicsGetCurrentContext (); CGSize size = self.bounds.size; CGContextSaveGState (context); { CGContextTranslateCTM (context, offset.x, offset.y); // Account for retina-sized graphics. CGContextScaleCTM (context, size.width / CGImageGetWidth(mask), size.height / CGImageGetHeight(mask)); CGRect rect = { {0.0, 0.0}, { CGImageGetWidth(mask), CGImageGetHeight(mask) } }; CGContextClipToMask (context, rect, mask); [color set]; UIRectFill (rect); } CGContextRestoreGState (context); } // fillMask
CGContextRef context = ...; ... CGContextSaveGState (context); { [drawable drawWithContext: context inRect: bounds withMetrics: self.metrics]; // or whatever drawing you're doing } CGContextRestoreGState (context);Edit: Gus Meuller of Flying Meat fame has a block-based utility that does this:
void FMCGContextHoldGState (CGContextRef context, void (^block)()) { CGContextSaveGState(context); { block (); } CGContextRestoreGState(context); }and if you're wanting to do something similar with
NSGraphicsContext
s:
void FMNSContextHoldGState (void (^block)()) { [NSGraphicsContext saveGraphicsState]; { block (); } [NSGraphicsContext restoreGraphicsState]; }
CFRelease
when done.
- (CGPathRef) newPathForRoundRect: (CGRect) rect radius: (CGFloat) radius strokeWidth: (CGFloat) strokeWidth { // Fit the stroked path inside the rectangle. rect.size.height -= strokeWidth; rect.size.width -= strokeWidth; rect.origin.x += strokeWidth / 2.0; rect.origin.y += strokeWidth / 2.0; CGMutablePathRef path = CGPathCreateMutable(); // The inner rect size gives us X/Y/W/H values for the parts of the rect // that aren't on the curve. CGRect innerRect = CGRectInset(rect, radius, radius); CGFloat insideRight = innerRect.origin.x + innerRect.size.width; CGFloat outsideRight = rect.origin.x + rect.size.width; CGFloat insideBottom = innerRect.origin.y + innerRect.size.height; CGFloat outsideBottom = rect.origin.y + rect.size.height; CGFloat insideTop = innerRect.origin.y; CGFloat outsideTop = rect.origin.y; CGFloat outsideLeft = rect.origin.x; CGPathMoveToPoint (path, NULL, innerRect.origin.x, outsideTop); CGPathAddLineToPoint (path, NULL, insideRight, outsideTop); CGPathAddArcToPoint (path, NULL, outsideRight, outsideTop, outsideRight, insideTop, radius); CGPathAddLineToPoint (path, NULL, outsideRight, insideBottom); CGPathAddArcToPoint (path, NULL, outsideRight, outsideBottom, insideRight, outsideBottom, radius); CGPathAddLineToPoint (path, NULL, innerRect.origin.x, outsideBottom); CGPathAddArcToPoint (path, NULL, outsideLeft, outsideBottom, outsideLeft, insideBottom, radius); CGPathAddLineToPoint (path, NULL, outsideLeft, insideTop); CGPathAddArcToPoint (path, NULL, outsideLeft, outsideTop, innerRect.origin.x, outsideTop, radius); CGPathCloseSubpath (path); return path; } // newPathForRoundRectIf you're not in CGLand (or not needing the same code to work on the desktop and device), there's also UIBezierPath's
-bezierPathWithRoundedRect:cornerRadius:
(for all corners), and -bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:
(for some arbitrary subset of corners), and NSBezierPath's -bezierPathWithRoundedRect:xRadius:yRadius:
. Muchos Thankos to Paul Collins for the reminder.- (void) makeLayerInContext: (CGContextRef) enclosingContext { layer = CGLayerCreateWithContext (enclosingContext, CGSizeMake(100, 100), NULL); // options - unused in Tiger CGContextRef context; context = CGLayerGetContext (layer); // .. and do your drawing } // makeLayerAnd then to draw the layer at a particular point (like replicating it a bunch of different points as done here):
for (i = 0; i < pointCount; i++) { CGContextDrawLayerAtPoint (context, locations[i], layer); }
-init
):
_timeIndicatorImage = [[[UIImage imageNamed: @"ride-profile-time-indicator"] stretchableImageWithLeftCapWidth: 0.0 topCapHeight: 1.0] retain];Then draw it. In this case it's a vertical indicator.
- (void) drawTimeIndicatorInRect: (CGRect) rect { CGFloat timeFraction = _time / _totalDuration; CGFloat x = [self horizontalPositionForTimeFraction: timeFraction]; CGRect indicatorRect = CGRectMake (x, 0, 3.0, rect.size.height); [_timeIndicatorImage drawInRect: indicatorRect]; } // drawTimeIndicatorInRect
Sizes are square. Assuming a universal app 512 iTunesArtwork iTunes. No .png extension 57 Icon.png AppStore, iPhone/Touch home screen. Required 114 Icon@2x.png iPhone4 72 Icon-72.png App Store, iPad. Required. 29 Icon-Small.png Settings on iPad and iPhone, Spotlight iPhone 50 Icon-Small-50.png Spotlight iPad 58 Icon-Small@2x.png Settings and Spotlight iPhone4Then in your info.plist, make an "Icon files" entry (an array), and list all the Icon*.png files.
- (NSBezierPath *) makePathFromString: (NSString *) string forFont: (NSFont *) font { NSTextView *textview; textview = [[NSTextView alloc] init]; [textview setString: string]; [textview setFont: font]; NSLayoutManager *layoutManager; layoutManager = [textview layoutManager]; NSRange range; range = [layoutManager glyphRangeForCharacterRange: NSMakeRange (0, [string length]) actualCharacterRange: NULL]; NSGlyph *glyphs; glyphs = (NSGlyph *) malloc (sizeof(NSGlyph) * (range.length * 2)); [layoutManager getGlyphs: glyphs range: range]; NSBezierPath *path; path = [NSBezierPath bezierPath]; [path moveToPoint: NSMakePoint (20.0, 20.0)]; [path appendBezierPathWithGlyphs: glyphs count: range.length inFont: font]; free (glyphs); [textview release]; return (path); } // makePathFromString