Best way to paint multiple rectangle? - ios5

In my project I need to paint couple of hundred rectangles using Quartz painting. I do have such a code
-(void)RenderRectangles:(NSArray*)rectangles
fillColor:(UIColor*)fillColor
strokeColor:(UIColor*)strokeColor
strokeThickness:(float)strokeThickness;
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIGraphicsPushContext(context);
CGContextSetStrokeColorWithColor(context, [strokeColor CGColor]);
CGContextSetLineWidth(context, strokeThickness);
for (NSValue *vRect in rectangles) {
CGContextAddRect(context, [vRect CGRectValue]);
}
CGContextStrokePath(context);
CGContextSetFillColorWithColor(context, [fillColor CGColor]);
for (NSValue *vRect in rectangles) {
CGContextFillRect(context, [vRect CGRectValue]);
}
UIGraphicsPopContext();
}
It works ok, but I'm just wondering if is possible to do it using only one loop? Or is there a better way to stroke and fill a collection of rectangles?
Thx

It's more efficient to create a single path. Check out the following functions to create a single path with your rectangles, then you can fill and stroke the same path instead of needing to recreate it:
CGPathCreateMutable()
CGPathAddRect()/CGPathAddRects()
CGContextAddPath()
...and for performance, remember to cache this path if you're going to draw it multiple times!

Related

How can I reset or clear the clipping mask associated with a CGContext?

I'm drawing to a CGContext and using a CGImageRef-based mask:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToMask(context, rect, _firstMaskImageRef);
CGContextSetFillColorWithColor(context, color);
CGContextFillRect(context, rect);
I have a second mask that I then want to switch to:
CGContextClipToMask(context, rect, _secondMaskImageRef);
CGContextSetFillColorWithColor(context, color); // color has changed FWIW
CGContextFillRect(context, rect); // as has rect
But, this intersects the two masks instead of replacing the first.
How do you (if you can) clear out or reset the clipping mask for a CGContext?
You can save the graphics state before you set the clipping mask, and then reset it afterwards, like this:
CGContextSaveGState (context);
...Set your first clipping mask, fill it, etc.
CGContextRestoreGState (context);
...Do other stuff
To reset the clipping mask, use:
CGContextResetClip(context);
Later versions of Swift (3+?) use the following syntax instead:
context.saveGState()
// set your clipping mask, etc.
context.restoreGState()
// everything's back to normal

On iOS, how to fill a path with an outline? (or both fill and stroke the path)

On iOS, if we do
CGContextMoveToPoint(contextFoo, 0, 0);
CGContextAddLineToPoint(contextFoo, x, y);
CGContextAddLineToPoint(contextFoo, x2, y2);
// ... and many more CGContextAddLineToPoint
then after this, if we do a CGContextStrokePath, we will get an outline, or if we CGContextFillPath, we get the "fill", but we can't do both, because after a stroke or a fill, the "current path" is gone. How can we both fill and stroke a path (such as wanting a yellow fill and orange outline)?
We can move the MoveTo and AddLine calls to a function, and call that function, do a fill, and call the function again, and do a stroke, but there are many x and y that makes passing all of them to the function quite troublesome. What might be some ways to do this?
CGContextDrawPath(context, kCGPathFillStroke);
// to both Fill and Stroke your context
// or kCGPathFill/kCGPathStroke to only fill/stroke
you can save your Path btw (to reuse it for multiple things of same shape) using:
CGPathBeginPath/MoveToPoint/AddLine/... very similar to CGContext/...
For your example:
[[UIColor yellowColor] setFill];
[[UIColor orangeColor] setStroke];
CGContextDrawPath(context, kCGPathFillStroke);

CGContextFillEllipseInRect acting weird

Here is my drawRect:
- (void)drawRect:(CGRect)rect
{
CGContextFillEllipseInRect(UIGraphicsGetCurrentContext(), self.bounds);
}
and the instances of this class are added a subviews the next way:
(#define DOTS_SIZE 30)
[self addSubview:[[VertexView alloc] initWithFrame:CGRectMake(anchor.x-DOTS_SIZE/2, anchor.y-DOTS_SIZE/2, DOTS_SIZE, DOTS_SIZE)]];
As far as I understand, I should get and ellipse (circle my case) in the views bounds. But I get it fully filled with rectangle (square).
By the way, I have logged bounds and their size is 30x30, so I should get nice little circles, but I get squares (T_T)
I'll be thankful for any advise!
The problem is that you have to make some setup before drawing the ellipse. For example:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctx, [UIColor redColor].CGColor); // Or any other color.
CGContextFillEllipseInRect(ctx, self.bounds);
}
And to make background transparent you can check out: Setting A CGContext Transparent Background

Draw squares with Core Graphics or include pngs?

I'm making a legend for a graph that will basically look like this:
[ ] Line 1
[ ] Line 2
[ ] Line 3
The boxes on the left need to be the same color as the lines on the graph.
Anyhow, all I need to know, is whether it's faster to draw the boxes with Core Graphics or just make some pngs with GIMP for the squares and include them.
Use UIView for each legend and set their background color to the color you want.
Both approaches are fast enough that it shouldn't make a difference. However, using Core Graphics has the advantage that you're a lot more flexible, e.g. when you later decide that you need additional colors. Plus, your app will be smaller, because you don't have to include the PNG files.
Drawing boxes is a snap! I would go with Core Graphics everyday, especially since you get retina support for free.
As can be seen in this example you can do it using UIKit only classes:
// Setup colors
[myBoxColor setFill];
[myBoxBorderColor set];
// Setup a path for the box
UIBezierPath* path = [UIBezierBath bezierPathWithRect:rectOfTheBox];
path.lineWidth = 2;
// Draw!
[path fill];
[path stroke];
One warning; stroke fills using the edges of the path as the center of the line. So you will get a blurry line if you stroke a path with integral rect with a 1 point line width.
You can remedy this is you want a 1 point line for the border by doing something like this:
CGRect strokeRect = UIEdgeInsetsInsetRect(rectOfTheBox,
UIEdgeInsetsMake(0.5f,0.5f,0.5f,0.5f));
UIBezierPath* path = [UIBezierPath bezierPathWithRect:strokeRect];
[path stroke];
On iOS, Core Graphics is quite simple to use. In your view's drawRect: method, just do this to draw a square:
- (void)drawRect:(CGRect)frame {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0.5, 0.5, 0.5, 1); // gray
CGContextFillRect(context, CGRectMake(10, 10, 20, 20)); // our rect is {10,10,20,20)
// draw a line
CGContextSetRGBStrokeColor(context, 1, 0, 0, 1);
CGContextBeginPath(context);
CGContextMoveToPoint(context, startX, startY);
CGContextAddLineToPoint(context, endX, endY);
CGContextStrokePath(context);
}
Hope this helps!

Texture from UIColor?

I am drawing a pie chart, each slice has a different color. I need to give the slices a textured look, not just the plain color. Any ideas how to do this? I don't want to use a image to use as a texture for all the possible colors. So I need to generate a texture or something like that. Any ideas. Thank You!
ps: this is an iphone project. (I can't use Core Image)
Use colorWithPatternImage with UIColor.
Edit: Sorry should have read the question properly.
You will need to use a UIGraphicsContext to create an image you can use in colorWithPatternImage. I would suggest using a grayscale image that you can load in, tint with a similar method to this, then use as a pattern in UIColor.
So you would have a method along the lines of this:
- (UIColor *)texturedPatternWithTint:(UIColor *)tint {
UIImage *texture = [UIImage imageNamed:#"texture.png"];
CGRect wholeImage = CGRectMake(0, 0, texture.size.width, texture.size.height);
UIGraphicsBeginImageContextWithOptions(texture.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, wholeImage, texture.CGImage);
CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextSetFillColor(context, CGColorGetComponents(tint.CGColor));
CGContextFillRect(context, self.bounds);
UIImage *tintedTexture = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [UIColor colorWithPatternImage:tintedTexture];
}
(not tested)