How to draw a circle pixel by pixel? - ios

I am able to draw a square pixel by pixel as below
for(int i=0 ;i<drawbox.size.width/2;i++)
{
for (int j=0; j<drawbox.size.height/2; j++)
{
Point.y++;
NSLog(#"point:%f,%f",Point.x,Point.y);
}
Point.x++;
}
Here drawrect is CGRect and Point is the CGPoint I am using to draw pixel by pixel
I iterate over this and find a square to be made. This square is filled with each pixel so it just not draw a square with border but it includes all pixels within the square.
I want the same thing but for circle (filled circle's pixels).
How can I achieve this?

Override your drawRect with following code :
You need to take care of 5 things :
SIDE_WEITH = Width of Circle,
Color constants :
_r = Red
_g = Green
_b = Blue
_a = Alpha
And set Progress as per your need : _progress
That it.
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
//// [image drawInRect:rect];
// find the radius and position for the largest circle that fits in the UIView's frame.
int radius, x, y;
int offset = SIDE_WEITH;
// in case the given frame is not square (oblong) we need to check and use the shortest side as our radius.
CGRect frame = self.frame;
if (frame.size.width > frame.size.height) {
radius = frame.size.height;
// we want our circle to be in the center of the frame.
int delta = frame.size.width - radius;
x = delta/2 - 1;
y = 0;
} else {
radius = frame.size.width;
int delta = frame.size.height - radius;
y = delta/2 - 1;
x = 0;
}
// store the largest circle's position and radius in class variable.
_outerCircleRect = CGRectMake(x, y, radius, radius);
// store the inner circles rect, this inner circle will have a radius 10pixels smaller than the outer circle.
// we want to the inner circle to be in the middle of the outer circle.
//_innerCircleRect = CGRectMake(x+offset, y+offset, radius-2*offset , radius-2*offset);
_innerCircleRect = CGRectMake(x+offset, y+offset, radius-2*offset , radius-2*offset);
// get the drawing canvas (CGContext):
CGContextRef context = UIGraphicsGetCurrentContext();
// save the context's previous state:
CGContextSaveGState(context);
// our custom drawing code will go here:
// Draw the gray background for our progress view:
// gradient properties:
CGGradientRef myGradient;
// You need tell Quartz your colour space (how you define colours), there are many colour spaces: RGBA, black&white...
CGColorSpaceRef myColorspace;
// the number of different colours
size_t num_locations = 3;
// the location of each colour change, these are between 0 and 1, zero is the first circle and 1 is the end circle, so 0.5 is in the middle.
CGFloat locations[3] = { 0.0, 0.5 ,1.0 };
// this is the colour components array, because we are using an RGBA system each colour has four components (four numbers associated with it).
CGFloat components[12] = {
0.4, 0.4, 0.4, 0.9, // Start colour
0.9, 0.9, 0.9, 1.0, // middle colour
0.4, 0.4, 0.4, 0.9
}; // End colour
myColorspace = CGColorSpaceCreateDeviceRGB();
myGradient = CGGradientCreateWithColorComponents (myColorspace, components,locations, num_locations);
// gradient start and end points
CGPoint myStartPoint, myEndPoint;
CGFloat myStartRadius, myEndRadius;
myStartPoint.x = _innerCircleRect.origin.x + _innerCircleRect.size.width/2;
myStartPoint.y = _innerCircleRect.origin.y + _innerCircleRect.size.width/2;
myEndPoint.x = _innerCircleRect.origin.x + _innerCircleRect.size.width/2;
myEndPoint.y = _innerCircleRect.origin.y + _innerCircleRect.size.width/2;
myStartRadius = _innerCircleRect.size.width/2 ;
myEndRadius = _outerCircleRect.size.width/2;
// draw the gradient.
/*CGContextDrawRadialGradient(context,
myGradient,
myStartPoint, myStartRadius, myEndPoint, myEndRadius, 0);
CGGradientRelease(myGradient);*/
// draw outline so that the edges are smooth:
// set line width
//CGContextSetLineWidth(context, 1);
// set the colour when drawing lines R,G,B,A. (we will set it to the same colour we used as the start and end point of our gradient )
/*CGContextSetRGBStrokeColor(context, 0.4,0.4,0.4,0.9);
// draw an ellipse in the provided rectangle
CGContextAddEllipseInRect(context, _outerCircleRect);
CGContextStrokePath(context);*/
/*CGContextAddEllipseInRect(context, _innerCircleRect);
CGContextStrokePath(context);*/
// Draw the progress:
// First clip the drawing area:
// save the context before clipping
CGContextSaveGState(context);
CGContextMoveToPoint(context,
_outerCircleRect.origin.x + _outerCircleRect.size.width/2, // move to the top center of the outer circle
_outerCircleRect.origin.y +1); // the Y is one more because we want to draw inside the bigger circles.
// add an arc relative to _progress
CGContextAddArc(context,
_outerCircleRect.origin.x + _outerCircleRect.size.width/2,
_outerCircleRect.origin.y + _outerCircleRect.size.width/2,
_outerCircleRect.size.width/2-1,
-M_PI/2,
(-M_PI/2 + _progress*2*M_PI), 0);
CGContextAddArc(context,
_outerCircleRect.origin.x + _outerCircleRect.size.width/2,
_outerCircleRect.origin.y + _outerCircleRect.size.width/2,
_outerCircleRect.size.width/2 - 9,
(-M_PI/2 + _progress*2*M_PI),
-M_PI/2, 1);
// use clode path to connect the last point in the path with the first point (to create a closed path)
CGContextClosePath(context);
// clip to the path stored in context
CGContextClip(context);
// Progress drawing code comes here:
// set the gradient colours based on class variables.
CGFloat components2[12] = { _r, _g, _b, _a, // Start color
((_r + 0.5 > 1) ? 1 : (_r+0.5) ) , ((_g + 0.5 > 1) ? 1 : (_g+0.5) ), ((_b + 0.5 > 1) ? 1 : (_b+0.5) ), ((_a + 0.5 > 1) ? 1 : (_a+0.5)),
_r, _g, _b, _a }; // End color
myGradient = CGGradientCreateWithColorComponents (myColorspace, components2,locations, num_locations);
myStartPoint.x = _innerCircleRect.origin.x + _innerCircleRect.size.width/2;
myStartPoint.y = _innerCircleRect.origin.y + _innerCircleRect.size.width/2;
myEndPoint.x = _innerCircleRect.origin.x + _innerCircleRect.size.width/2;
myEndPoint.y = _innerCircleRect.origin.y + _innerCircleRect.size.width/2;
// set the radias for start and endpoints a bit smaller, because we want to draw inside the outer circles.
myStartRadius = _innerCircleRect.size.width/2;
myEndRadius = _outerCircleRect.size.width/2;
CGContextDrawRadialGradient(context,
myGradient,
myStartPoint, myStartRadius, myEndPoint, myEndRadius, 0);
// release myGradient and myColorSpace
CGGradientRelease(myGradient);
CGColorSpaceRelease(myColorspace);
// draw circle on the outline to smooth it out.
CGContextSetRGBStrokeColor(context, _r,_g,_b,_a);
// draw an ellipse in the provided rectangle
CGContextAddEllipseInRect(context, _outerCircleRect);
CGContextStrokePath(context);
CGContextAddEllipseInRect(context, _innerCircleRect);
CGContextStrokePath(context);
//restore the context and remove the clipping area.
CGContextRestoreGState(context);
// restore the context's state when we are done with it:
CGContextRestoreGState(context);
/*CGPathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath , NULL , rect);
CAShapeLayer *circle = [[CAShapeLayer alloc] init];
circle.path = circlePath;
circle.opacity = 0.5;
[self.imageView.layer addSublayer:circle];
CGPathRelease( circlePath );
[circle release];*/
}

for (int i=0; i<drawbox.size.width/2; i++) {
for (int j=(int)(-drawbox.size.height/4.0 * sqrt(1 - pow(4.0*i/drawbox.size.width - 1, 2)) + drawbox.size.height/4.0);
j<=(int)(drawbox.size.height/4.0 * sqrt(1 - pow(4.0*i/drawbox.size.width - 1, 2)) + drawbox.size.height/4.0);
j++)
{
Point.y++;
NSLog(#"point:%f,%f",Point.x,Point.y);
}
tStartPoint.x++;
}
This will draw an ellipse with the same center the rectangle has you drow in the question.

Related

CGContextMoveToPoint moves to wrong position

I'm new to core graphics and I'm struggling with a simple task of putting a sweeping circle inside a square. The outcome I got looks like this:
The circle won't appear at the center of the square, and the size of the circle appears much smaller than I specified.
Below is my drawRect method for drawing the circle. I have put the printed-out variable values while debugging in the comments for your convenience. I also printed out the value passed to initWithFrame: frame=(0 0; 256 256). The frame is the orange square you see in the picture.
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat midX = CGRectGetMidX(self.bounds); // bounds = (0 0; 256 256); midX = 128
CGFloat midY = CGRectGetMidY(self.bounds); // midY = 128
CGFloat radius = midY - 4; // radius = 124
// Outer grey pie
[endColor setFill];
CGContextMoveToPoint(context, midX, midY); // move to center
CGContextAddEllipseInRect(context, CGRectMake(midX - radius, midY - radius, radius * 2, radius * 2)); // adds a circle of radius = square_side_length - 4
CGContextFillPath(context); // fill the circle above with grey
// Show the clock
NSTimeInterval seconds = [[NSDate date] timeIntervalSince1970];
CGFloat mod = fmod(seconds, self.period);
CGFloat percent = mod / self.period;
[fillColor setFill];
CGFloat start = -M_PI_2;
CGFloat end = 2 * M_PI;
CGFloat sweep = end * percent + start;
CGContextMoveToPoint(context, midX, midY);
CGContextAddArc(context, midX, midY, radius, start, sweep, 0); // radius = square_side_length - 24
CGContextFillPath(context);
// Innermost white pie
radius -= 50; // radius = square_side_length - 54
[bgColor setFill]; // white
CGContextMoveToPoint(context, midX, midY);
CGContextAddEllipseInRect(context, CGRectMake(midX - radius, midY - radius, radius * 2, radius * 2));
CGContextFillPath(context);
}
And below is the code that adds the clock to its superview:
clock = [[ProgressClock alloc] initWithFrame:self.clockHolder.bounds // bounds=[0 0; 256 256]
period:[TOTPGenerator defaultPeriod]
bgColor:[UIColor whiteColor]
strokeColor:[UIColor colorWithWhite:0 alpha:0.2]
fillColor:[UIColor blueColor]
endColor:[UIColor grayColor]
shade:NO];
[self.clockHolder addSubview:clock];
Can anyone spot the mistake I made? Thanks in advance.
Thanks a lot to #originaluser2's comment, I have fixed this issue simply by moving the clock presenting logic from viewDidLoad to viewDidAppear and the clock showed up perfectly. There was nothing wrong with the drawing code I posted; however the auto-layout initialization and the animation of my clock happened in a sequence that gave my drawing canvas a wrong frame. By putting the drawing logic in viewDidAppear, we are guaranteed that all the auto-layout setup has been completed, thus frames are fixed, before continue onto drawing the circle.

Using Core Graphics from ObjectiveC, how to you curve an arrow around a circle?

Our designer has asked me to recreate this:
Im subclassing UIView, and I've overridden the drawRect command like this:
[super drawRect:frame];
CGFloat x = self.frame.origin.x;
CGFloat y = self.frame.origin.y;
CGFloat w = self.frame.size.width;
CGFloat h = self.frame.size.height;
CGFloat lineWidth = lineWidthRequested;
CGPoint centerPoint = CGPointMake(w/2, h/2);
CGFloat radius = radiusRequested;
CGFloat startAngle = 3 * M_PI / 2;
CGFloat endAngle = startAngle + percentage * 2 * M_PI;
CGMutablePathRef arc = CGPathCreateMutable();
CGPathAddArc(arc, NULL,
centerPoint.x, centerPoint.y,
radius,
startAngle,
endAngle,
NO);
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(arc, NULL,
lineWidth,
kCGLineCapButt,
kCGLineJoinMiter, // the default
10); // 10 is default miter limit
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, strokedArc);
CGContextSetFillColorWithColor(c, [UIColor colorWithRed:239/255.0 green:101/255.0 blue:47/255.0 alpha:1.0].CGColor);
CGContextDrawPath(c, kCGPathFill);
What I ended up with is this:
Gotta still draw the arrowhead. Gonna be easy, right?
After struggling to remember my trig, I found rotation of points around a center on this page:
Rotating a point around an origin in VB
But when I tried translation to objective C to draw the arrowhead, I'm getting very odd results. Here's the code further down in drawRect:
CGFloat triangle[3][2] = {{centerPoint.x + 10, h - (centerPoint.y + radius)},
{centerPoint.x, h - (centerPoint.y + radius + lineWidth/2)},
{centerPoint.x, h - (centerPoint.y + radius - lineWidth/2)}};
for (int idx=0;idx < 3; idx++) {
// translate to origin
triangle[idx][0] -= centerPoint.x;
triangle[idx][1] -= centerPoint.y;
}
CGFloat angDistance = endAngle - startAngle;
CGFloat ct = cos(angDistance);
CGFloat st = sin(angDistance);
for (int idx=0;idx < 3; idx++) {
// rotate
triangle[idx][0] = ct * triangle[idx][0] - st * triangle[idx][1];
triangle[idx][1] = -st * triangle[idx][0] + ct * triangle[idx][1];
}
for (int idx=0;idx < 3; idx++) {
// translate back to position
triangle[idx][0] += centerPoint.x;
triangle[idx][1] += centerPoint.y;
}
NSLog(#"Rotating through %g, %06.1f,%06.1f , ct - %g, st - %g",angDistance, triangle[0][0],triangle[0][1],ct, st);
// XXX todo draw the filled triangle at end.
// draw a red triangle, the point of the arrow
CGContextSetFillColorWithColor(c, [[UIColor greenColor] CGColor]);
CGContextMoveToPoint(c, triangle[0][0], triangle[0][1]);
CGContextAddLineToPoint(c, triangle[1][0], triangle[1][1]);
CGContextAddLineToPoint(c, triangle[2][0], triangle[2][1]);
CGContextFillPath(c);
I was expecting that I make these points, then translate them to an origin, rotate, and then translate them back, I'd be laughing.
However, that's not what's happening...as the percentage increases from 0 to 2pi, the arrowhead draws itself in a vaguely triangular route. When the angDistance is zero or pi, the arrowhead is in the right place. As I head towards pi/2 or 3pi/2 though, the arrowhead heads off towards the lower corners of an enclosing rect.
I must be doing something blatantly stupid, but I can't for the life of me see it.
Any ideas?
Thanks,
-Ken
I'd suggest constructing a path for the entire outline of the desired shape and then "fill" that path with the desired color. That eliminates any risk of any gaps or anything not quite lining up.
Thus, this path might consisting of an arc for the outside of the arrow, two lines for the head of the arrow, an arc back for the inside of the arrow, and then close the path. That might look like:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.arrowColor.CGColor);
CGPoint center = CGPointMake(rect.size.width / 2.0, rect.size.height / 2.0);
CGContextMoveToPoint(context, center.x + cosf(self.startAngle) * (self.radius + self.lineWidth / 2.0),
center.y + sinf(self.startAngle) * (self.radius + self.lineWidth / 2.0));
CGContextAddArc(context, center.x, center.y, self.radius + self.lineWidth / 2.0, self.startAngle, self.endAngle, !self.clockwise);
CGFloat theta = asinf(self.lineWidth / self.radius / 2.0) * (self.clockwise ? 1.0 : -1.0);
CGFloat pointDistance = self.radius / cosf(theta);
CGContextAddLineToPoint(context, center.x + cosf(self.endAngle + theta) * pointDistance,
center.y + sinf(self.endAngle + theta) * pointDistance);
CGContextAddLineToPoint(context, center.x + cosf(self.endAngle) * (self.radius - self.lineWidth / 2.0),
center.y + sinf(self.endAngle) * (self.radius - self.lineWidth / 2.0));
CGContextAddArc(context, center.x, center.y, self.radius - self.lineWidth / 2.0, self.endAngle, self.startAngle, self.clockwise);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFill);
}
The only trick here was coming up with the right point for the end of the arrow. I've improved the choice to handle fatter arrows a little better, but you should feel free to use whatever you feel is best for your application.
Thus, the following code:
self.arrowView.radius = 100;
self.arrowView.arrowColor = [UIColor blueColor];
self.arrowView.lineWidth = 40;
self.arrowView.startAngle = -M_PI_2;
self.arrowView.endAngle = M_PI;
self.arrowView.clockwise = TRUE;
would yield the following (which I'm animating with a CADisplayLink):
This uses the start angle of zero as meaning the "3 o'clock" position, but you can obviously tweak this as you see fit. But hopefully it illustrates one approach to the problem.
By the way, while I've answered the question of how to do this with CoreGraphics, I wouldn't necessarily suggest doing so. For example, in https://github.com/robertmryan/CircularArrowDemo, I don't implement drawRect, but instead update a CAShapeLayer. By doing this, not only do I avoid drawRect inefficiencies, but one could theoretically also change how you use this CAShapeLayer (e.g. use it as a mask for some UIView, revealing some more interesting color gradation (or other image) behind it).
Here is another solution (not very scalable though). This solution assumes this is like a logo where the angle/percent of circle drawn will not change.
- (void)drawRect:(CGRect)rect {
UIBezierPath *circleOutline = [UIBezierPath bezierPath];
[self.circleColor setStroke];
[circleOutline setLineWidth:self.bounds.size.width*0.15];
[circleOutline addArcWithCenter:CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2) radius:self.bounds.size.width/2-circleOutline.lineWidth/2 startAngle:3*M_PI/2 endAngle:3*M_PI/4 clockwise:YES];
[circleOutline stroke];
[self addArrowView:circleOutline];
}
- (void)addArrowView:(UIBezierPath *)path {
for (int x = 0; x < self.bounds.size.width/2; x++) {
for (int y = self.bounds.size.height/2; y < self.bounds.size.height; y++) {
if ([path containsPoint:CGPointMake(x, y)]) {
// Pythagorean Theorem - We want the diagonal length of the square to be lineWidth, so we need to calculate what size
// to make each side of the square to make the diagonal equal to lineWidth
double sideLength = sqrt((path.lineWidth*path.lineWidth)/2);
UIView *arrowView = [[UIView alloc] initWithFrame:CGRectMake(x-sideLength/2, y-sideLength/2, sideLength, sideLength)];
arrowView.backgroundColor = self.circleColor;
[self addSubview:arrowView];
return;
}
}
}
}
would yield:

Clip CGPath and fill with two different colours

I'm in the process of creating a custom 'star' control in that you would be able to pass a float into the control as the rating i.e. 2.5, and 2.5 out of 5 stars would be coloured red and the rest, gray.
I'm drawing the stars using a UIBezierPath with 5 points and this is working perfectly. However, as I am using floats, I need to make sure that the decimals are taken into account. I thought that the best way to accomplish this would be by clipping the bezier path to a proportion of the final width, however, this method doesn't seem to have any effect on the drawing itself; the stars are drawn as normal, not taking into account the decimals.
As you probably expected me to say, I have indeed only just started dabbling in CoreGraphics and would like an explanation as to why my method doesn't work and a method to fix it, in order to help with my progression through the framework.
Look forward to hearing some responses!
- (void)drawStarsWithRating:(float)rating maxRating:(float)maxRating yOrigin:(CGFloat)yOrigin inContext:(CGContextRef)context {
float width = MKRGlyphSize;
CGFloat xCenter = MKRLeftBorderPadding + (0.5 * width);
CGFloat yCenter = yOrigin + (0.5 * width);
double r = width / 2.0;
float flip = -1.0;
for (NSUInteger i = 0; i < maxRating; i++) {
CGContextSaveGState(context);
if (i < rating) {
if (self.selected) {
CGContextSetFillColorWithColor(context, RGB(125, 212, 67).CGColor);
}
else {
CGContextSetFillColorWithColor(context, RGB(215, 35, 32).CGColor);
}
}
else {
if (self.selected) {
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
}
else {
CGContextSetFillColorWithColor(context, RGB(178, 178, 178).CGColor);
}
}
double theta = 2.0 * M_PI * (2.0 / 5.0);
UIBezierPath *bezier = [UIBezierPath bezierPath];
[bezier moveToPoint:CGPointMake(xCenter, r * flip + yCenter)];
for (NSUInteger k = 1; k < 5; k++) {
float x = r * sin(k * theta);
float y = r * cos(k * theta);
[bezier addLineToPoint:CGPointMake(x + xCenter, y * flip + yCenter)];
}
[bezier setLineWidth:1.0f];
[bezier setLineJoinStyle:kCGLineJoinMiter];
[bezier closePath];
[bezier fill];
if (rating - floorf(rating) > 0) {
CGRect clipRect = CGRectMake(xCenter, yOrigin, width * (rating - floorf(rating)), width);
CGContextClipToRect(context, clipRect);
}
xCenter += width;
CGContextRestoreGState(context);
}
}
One problem I noticed is that you [fill] the path only once. this means for fractional stars you would've only seen one half of the star and not the other half. In the code below, each star is filled with white, and then if any portion of the star is within the rating, then it is filled again with blue.
I also noticed that the clipping rectangle you were using started its X at xCenter instead instead of the lefthand side of the star.
I also adjusted the math a bit to calculate the % filled for each star more consistently.
- (void)drawStarsWithRating:(float)rating maxRating:(float)maxRating yOrigin:(CGFloat)yOrigin inContext:(CGContextRef)context {
float width = MKRGlyphSize;
CGFloat xCenter = MKRLeftBorderPadding + (0.5 * width);
CGFloat yCenter = yOrigin + (0.5 * width);
double r = width / 2.0;
float flip = -1.0;
for (NSUInteger i = 0; i < maxRating; i++) {
CGContextSaveGState(context);
// for clarity, i removed the colors from the top
// and ignore selected state. i use blue/white
// colors hard coded below
//
// you can easily change those colors just as you
// had before
// create star path
double theta = 2.0 * M_PI * (2.0 / 5.0);
UIBezierPath *bezier = [UIBezierPath bezierPath];
[bezier moveToPoint:CGPointMake(xCenter, r * flip + yCenter)];
for (NSUInteger k = 1; k < 5; k++) {
float x = r * sin(k * theta);
float y = r * cos(k * theta);
[bezier addLineToPoint:CGPointMake(x + xCenter, y * flip + yCenter)];
}
[bezier setLineWidth:1.0f];
[bezier setLineJoinStyle:kCGLineJoinMiter];
[bezier closePath];
// fill background of star with white
[[UIColor whiteColor] setFill];
[bezier fill];
// calculate the percentage of this star
// that we should fill
CGFloat currStar = i;
CGFloat percentOfStar;
if(rating > currStar){
// at least some of the star should be filled
percentOfStar = rating - currStar > 0 ? rating - currStar : 0;
percentOfStar = percentOfStar > 1 ? 1 : percentOfStar;
}else{
// none of the star should be filled
percentOfStar = 0;
}
if (percentOfStar) {
// if we need at least a little filling, then clip to that % of the star
// notice (xCenter - .5*width) to align the clipRect to the left side of
// the star.
// now fill the selected portion of the star with blue
CGRect clipRect = CGRectMake(xCenter - .5*width, yOrigin, width * (percentOfStar), width);
CGContextClipToRect(context, clipRect);
[[UIColor blueColor] setFill];
[bezier fill];
}
xCenter += width;
CGContextRestoreGState(context);
}
}
You could do it slightly differently:
fill the background of your view with the background color
Use the percentOfStar to create a rectangle path that reflects the rating.
Use the star path to clip.

Drawing a pattern along a path

My goal is to take a pattern like this
and draw it repeatedly along a circular path to produce something similar to this image:
I found several code examples in other questions and an full demo project here but the result is this:
I think the difference between the two images is obvious, but I find it hard to describe (pardon my lack of graphics vocabulary). The result seems to be tiling without the desired rotation/deformation of the pattern. I think I can live with the lack of deformation, but the rotation is key. I think that perhaps the draw callback could/should be modified to include a rotation, but can't figure out how to retrieve/determine the angle at the point of the callback.
I considered an approach where I manually deformed/rotated the image and drew it several times around a centerpoint to achieve the effect I want, but I believe that CoreGraphics could do it with more efficiency and with less code.
Any suggestions about how to achieve the result I want would be appreciated.
Here is the relevant code from the ChalkCircle project:
const float kPatternWidth = 8;
const float kPatternHeight = 8;
void DrawPatternCellCallback(void *info, CGContextRef cgContext)
{
UIImage *patternImage = [UIImage imageNamed:#"chalk_brush.png"];
CGContextDrawImage(cgContext, CGRectMake(0, 0, kPatternWidth, kPatternHeight), patternImage.CGImage);
}
- (void)drawRect:(CGRect)rect {
float startDeg = 0; // where to start drawing
float endDeg = 360; // where to stop drawing
int x = self.center.x;
int y = self.center.y;
int radius = (self.bounds.size.width > self.bounds.size.height ? self.bounds.size.height : self.bounds.size.width) / 2 * 0.8;
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGRect patternBounds = CGRectMake(0, 0, kPatternWidth, kPatternHeight);
const CGPatternCallbacks kPatternCallbacks = {0, DrawPatternCellCallback, NULL};
CGAffineTransform patternTransform = CGAffineTransformIdentity;
CGPatternRef strokePattern = CGPatternCreate(
NULL,
patternBounds,
patternTransform,
kPatternWidth, // horizontal spacing
kPatternHeight,// vertical spacing
kCGPatternTilingNoDistortion,
true,
&kPatternCallbacks);
CGFloat color1[] = {1.0, 1.0, 1.0, 1.0};
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetStrokeColorSpace(ctx, patternSpace);
CGContextSetStrokePattern(ctx, strokePattern, color1);
CGContextSetLineWidth(ctx, 4.0);
CGContextMoveToPoint(ctx, x, y - radius);
CGContextAddArc(ctx, x, y, radius, (startDeg-90)*M_PI/180.0, (endDeg-90)*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextDrawPath(ctx, kCGPathStroke);
CGPatternRelease(strokePattern);
strokePattern = NULL;
CGColorSpaceRelease(patternSpace);
patternSpace = NULL;
}
.SOLUTION FROM SAM
I modified sam's solution to handle non-square patterns, center the result, and remove hard coded numbers by calculating them from the passed in image:
#define MAX_CIRCLE_DIAMETER 290.0f
#define OVERLAP 1.5f
-(void) drawInCircle:(UIImage *)patternImage
{
int numberOfImages = 12;
float diameter = (MAX_CIRCLE_DIAMETER * numberOfImages * patternImage.size.width) / ( (2.0 * M_PI * patternImage.size.height) + (numberOfImages * patternImage.size.width));
//get the radius, circumference and image size
CGRect replicatorFrame = CGRectMake((320-diameter)/2.0f, 60.0f, diameter, diameter);
float radius = diameter/2;
float circumference = M_PI * diameter;
float imageWidth = circumference/numberOfImages;
float imageHeight = imageWidth * patternImage.size.height / patternImage.size.width;
//create a replicator layer and add it to our view
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = replicatorFrame;
[self.view.layer addSublayer:replicator];
//configure the replicator
replicator.instanceCount = numberOfImages;
//apply a rotation transform for each instance
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI / (numberOfImages/2), 0, 0, 1);
replicator.instanceTransform = transform;
//create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
//the frame places the layer in the middle of the replicator layer and on the outside of
//the replicator layer so that the the size is accurate relative to the circumference
layer.frame = CGRectMake(radius - (imageWidth/2.0) - (OVERLAP/2.0), -imageHeight/2.0, imageWidth+OVERLAP, imageHeight);
layer.anchorPoint = CGPointMake(0.5, 1);
[replicator addSublayer:layer];
//apply a perspective transform to the layer
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0f / -radius;
perspectiveTransform = CATransform3DRotate(perspectiveTransform, (M_PI_4), -1, 0, 0);
layer.transform = perspectiveTransform;
//set the image as the layer's contents
layer.contents = (__bridge id)patternImage.CGImage;
}
Using Core Animation's replicator layer, I managed to create this result:
I think it's close to what your looking for. In this example all the images are square with a 3d X rotation applied to each of them.
#import <QuartzCore/QuartzCore.h>
//set the number of images and the diameter (width) of the circle
int numberOfImages = 30;
float diameter = 450.0f;
//get the radius, circumference and image size
float radius = diameter/2;
float circumference = M_PI * diameter;
float imageSize = circumference/numberOfImages;
//create a replicator layer and add it to our view
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = CGRectMake(100.0f, 100.0f, diameter, diameter);
[self.view.layer addSublayer:replicator];
//configure the replicator
replicator.instanceCount = numberOfImages;
//apply a rotation transform for each instance
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI / (numberOfImages/2), 0, 0, 1);
replicator.instanceTransform = transform;
//create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
//the frame places the layer in the middle of the replicator layer and on the outside of the replicator layer so that the the size is accurate relative to the circumference
layer.frame = CGRectMake(radius - (imageSize/2), -imageSize/2, imageSize, imageSize);
layer.anchorPoint = CGPointMake(0.5, 1);
[replicator addSublayer:layer];
//apply a perspective transofrm to the layer
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0f / -radius;
perspectiveTransform = CATransform3DRotate(perspectiveTransform, (M_PI_4), -1, 0, 0);
layer.transform = perspectiveTransform;
//set the image as the layer's contents
layer.contents = (__bridge id)[UIImage imageNamed:#"WCR3Q"].CGImage;

How To Draw UILabel On Pie Chart without Core Plot?

I have Drawn the pie-chart using following code:
1)created UIView Custom class Piechart
2)in drawRect
int c=[itemArray count];
[_label removeFromSuperview];
CGFloat angleArray[c];
offset = 0.0;
int sum=0;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, false);
CGContextSetShouldAntialias(context, false);
for(int i=0;i<[itemArray count];i++)
{
sum+=[[itemArray objectAtIndex:i] intValue];
}
for(int i=0;i<[itemArray count];i++)
{
angleArray[i]=(float)(([[itemArray objectAtIndex:i] intValue])/(float)sum)*(2*3.14); // in radians
CGContextMoveToPoint(context, radius, radius);
if(i==0)
{
CGContextAddArc(context, radius, radius, radius, 0,angleArray[i], 0);
}
else
{
CGContextAddArc(context, radius, radius, radius,offset,offset+angleArray[i], 0);
}
offset+=angleArray[i];
CGContextSetFillColorWithColor(context, ((UIColor *)[myColorArray objectAtIndex:i]).CGColor);
CGContextClosePath(context);
CGContextFillPath(context);
}
3) The problem is How to draw label on each sections.
You can convert polar coordinates to Cartesian coordinates by this formula
x = radius * cos(angle);
y = radius * sin(angle);
Point(x,y) can be the center of your label.
In your case angle is AVG between start and end angle. Radius is the distance between center of pie-chart and label.
- (CGPoint)getApproximateMidPointForArcWithStartAngle:(CGFloat)startAngle andDegrees:(CGFloat)endAngle {
NSLog(#"start angle %f, end angle %f",startAngle,endAngle);
CGFloat midAngle = DEGREES_TO_RADIANS((startAngle + endAngle) / 2);
NSLog(#"midangle %f ",midAngle);
CGFloat x=0;
CGFloat y=0;
x= (250) + (250 * cos(midAngle));
y= (250) + (250 * sin(midAngle));
CGPoint approximateMidPointCenter = CGPointMake(((x+250)/2), ((y+250)/2));
return approximateMidPointCenter; }
here approximateMidPointCenter is Mid-Point of line joining circle center and arc's center.
label.center=approximateMidPointCenter

Resources