Huge Memory Leak in CGMutablePathRef - iphone

I Have Rendered nearly 1000 Polygons in the map. I get the path of the polygon using
- (CGPathRef)polyPath:(MKPolygon *)polygon
{
MKMapPoint *points = [polygon points];
NSUInteger pointCount = [polygon pointCount];
NSUInteger i;
if (pointCount < 3)
return NULL;
CGMutablePathRef path = CGPathCreateMutable();
if([polygon isKindOfClass:[MKPolygon class]])
{
for (MKPolygon *interiorPolygon in polygon.interiorPolygons)
{
CGPathRef interiorPath = [self polyPath:interiorPolygon];
CGPathAddPath(path, NULL, interiorPath);
CGPathRelease(interiorPath);
}
}
CGPoint relativePoint = [self pointForMapPoint:points[0]];
CGPathMoveToPoint(path, NULL, relativePoint.x, relativePoint.y);
for (i = 1; i < pointCount; i++)
{
relativePoint = [self pointForMapPoint:points[i]];
CGPathAddLineToPoint(path, NULL, relativePoint.x, relativePoint.y);
}
return path;
}
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
MultiPolygon *multiPolygon = (MultiPolygon *)self.overlay;
for (MKPolygon *polygon in multiPolygon.polygons)
{
if([polygon isKindOfClass:[MKPolygon class]])
{
CGPathRef path = [self polyPath:polygon];
if (path)
{
[self applyFillPropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathEOFill);
[self applyStrokePropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextSetAlpha(context,1.0);
CGContextStrokePath(context);
}
CGPathRelease(path);
}
}
}
I get leak in
CGPathRelease(interiorPath);
and
return path;
I know that i have to release path using CGPathRelease but where to release it while i have to return.
Both leaks a huge memory.
I have been working on this for days, Please help.
Thanks in Advance

You should rename your method to be -createPolyPath: to make it clear that it is returning a Core Foundation object that needs to be released, then in the code in which you call -createPolyPath:, you need to release it like so:
CGPathRef path = [someObjectOrClass createPolyPath:somePolygon];
// Do some stuff with the path
CGPathRelease(path);
See the "Memory Management Programming Guide for Core Foundation":

I think that you must rename your method starting with new like newPolyPath.... I'll done that and it's work now for me with no more leaks on path...
You also must use CGPathRelease(path); after each use of your path.

Try using CGPathRelease(path);
For example:
CGMutablePathRef path = CGPathCreateMutable(); // created memory allocation
CGPathCloseSubpath(path);
CGPathRelease(path); // released path allocation
Great tip found here:
http://the.ichibod.com/kiji/ios-memory-management-tips/

Related

Draw lines between points(multiple)

with the help i am able to draw circle with the coordinated using:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 5.0);
CGContextSetStrokeColorWithColor(context,[UIColor grayColor].CGColor);
/**** Get values from reponse and fill it in this array. ******/
NSArray *objectCoords = [NSArray arrayWithObjects: #"{{20,80},{5,5}}", #"{{120,60},{5,5}}", #"{{60,84},{5,5}}", #"{{80,88},{5,5}}", #"{{100,93},{5,5}}", #"{{20,20},{5,5}}", #"{{160,70},{5,5}}", #"{{128,68},{5,5}}", #"{{90,60},{5,5}}", #"{{110,80},{5,5}}", nil];
for (NSString* objectCoord in objectCoords) {
CGRect coord = CGRectFromString(objectCoord);
// Draw using your coord
CGContextAddEllipseInRect(context, coord);
}
Now what i am trying to achieve is draw lines between the points(circle),as shown in the attached image.I know we can draw a line between 2 points, but here in this case, the lines needs to be drawn between one to multiple point/circle .Please suggest me to achieve this result.
You can just draw multiple lines. First, come up with a model to represent between which points you want to draw lines. For example, you could have an array of lines, where each line is defined, itself, as an array with the indices of two points, the starting point and the ending point.
For example, if you want to draw lines from point 1 to 3, 4, and 5, and from point 3 to 4 and 5, and between 4 and 5, you could do something like:
CGContextSetLineWidth(context, 1.0);
NSArray *lines = #[#[#(1), #(3)],
#[#(1), #(4)],
#[#(1), #(5)],
#[#(3), #(4)],
#[#(3), #(5)],
#[#(4), #(5)]];
for (NSArray *points in lines) {
NSInteger startIndex = [points[0] integerValue];
NSInteger endIndex = [points[1] integerValue];
CGRect startRect = CGRectFromString(objectCoords[startIndex]);
CGRect endRect = CGRectFromString(objectCoords[endIndex]);
CGContextMoveToPoint(context, CGRectGetMidX(startRect), CGRectGetMidY(startRect));
CGContextAddLineToPoint(context, CGRectGetMidX(endRect), CGRectGetMidY(endRect));
}
CGContextDrawPath(context, kCGPathStroke);
There are tons of different ways of doing it, but just come up with some model that represents where you want to draw the lines, and then iterate through that model to draw all of the individual lines.
If you wanted to have every point draw lines to the three closest points (which is not what your picture does, but it's what you asked for in a subsequent comment), you can:
build array of indices, indices in your objectCoords array; and
now iterate through each point in objectCoords:
build new array of indices, sortedIndices, sorted by the distance that the point represented by that index is from the current object in objectCoords; and
draw the three closest lines.
Thus:
// build array of indices (0, 1, 2, ...)
NSMutableArray *indices = [NSMutableArray array];
[objectCoords enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[indices addObject:#(idx)];
}];
// now go through all of the points in objectCoords
[objectCoords enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
// build new array of indices sorted by distance from the current point
NSArray *sortedIndices = [indices sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
CGFloat distance1 = [self distanceFromPointAtIndex:idx
toPointAtIndex:[obj1 integerValue]
usingObjectCoords:objectCoords];
CGFloat distance2 = [self distanceFromPointAtIndex:idx
toPointAtIndex:[obj2 integerValue]
usingObjectCoords:objectCoords];
if (distance1 < distance2)
return NSOrderedAscending;
else if (distance1 > distance2)
return NSOrderedDescending;
return NSOrderedSame;
}];
// now draw lines to the three closest indices
// (skipping 0, because that's probably the current point)
for (NSInteger i = 1; i < 4 && i < [sortedIndices count]; i++) {
NSInteger index = [sortedIndices[i] integerValue];
CGRect startRect = CGRectFromString(objectCoords[idx]);
CGRect endRect = CGRectFromString(objectCoords[index]);
CGContextMoveToPoint(context, CGRectGetMidX(startRect), CGRectGetMidY(startRect));
CGContextAddLineToPoint(context, CGRectGetMidX(endRect), CGRectGetMidY(endRect));
}
}];
CGContextSetLineWidth(context, 1);
CGContextDrawPath(context, kCGPathStroke);
And this uses the following method to calculate the distance between two points:
- (CGFloat)distanceFromPointAtIndex:(NSInteger)index1 toPointAtIndex:(NSInteger)index2 usingObjectCoords:(NSArray *)objectCoords
{
CGRect rect1 = CGRectFromString(objectCoords[index1]);
CGRect rect2 = CGRectFromString(objectCoords[index2]);
return hypotf(CGRectGetMidX(rect1) - CGRectGetMidX(rect2), CGRectGetMidY(rect1) - CGRectGetMidY(rect2));
}
Using the objectCoords in your original example, that yields:
This is a little unrelated to your original question, but rather than an array of strings, like so:
NSArray *objectCoords = #[#"{{20,80},{5,5}}",
#"{{120,60},{5,5}}",
#"{{60,84},{5,5}}",
#"{{80,88},{5,5}}",
#"{{100,93},{5,5}}",
#"{{20,20},{5,5}}",
#"{{160,70},{5,5}}",
#"{{128,68},{5,5}}",
#"{{90,60},{5,5}}",
#"{{110,80},{5,5}}"];
I might suggest employing an array of NSValue objects:
NSArray *objectCoords = #[[NSValue valueWithCGRect:CGRectMake(20,80,5,5)],
[NSValue valueWithCGRect:CGRectMake(120,60,5,5)],
[NSValue valueWithCGRect:CGRectMake(60,84,5,5)],
[NSValue valueWithCGRect:CGRectMake(80,88,5,5)],
[NSValue valueWithCGRect:CGRectMake(100,93,5,5)],
[NSValue valueWithCGRect:CGRectMake(20,20,5,5)],
[NSValue valueWithCGRect:CGRectMake(160,70,5,5)],
[NSValue valueWithCGRect:CGRectMake(128,68,5,5)],
[NSValue valueWithCGRect:CGRectMake(90,60,5,5)],
[NSValue valueWithCGRect:CGRectMake(110,80,5,5)]];
Then, when you're extracting the CGRect value, instead of:
CGRect rect = CGRectFromString(objectCoords[index]);
You would do:
CGRect rect = [objectCoords[index] CGRectValue];
I know this looks more cumbersome, but using NSValue is going to be more efficient (which is useful when doing a lot or repeated calculations of distances).
Even better, you might want to define your own model object that more intuitively defines a point that you want to chart, e.g.:
#interface ChartPoint : NSObject
#property (nonatomic) CGPoint center;
#property (nonatomic) CGFloat radius;
#end
and
#implementation ChartPoint
+ (instancetype) chartPointWithCenter:(CGPoint)center radius:(CGFloat)radius
{
ChartPoint *chartPoint = [[ChartPoint alloc] init];
chartPoint.center = center;
chartPoint.radius = radius;
return chartPoint;
}
- (CGFloat)distanceToPoint:(ChartPoint *)otherPoint
{
return hypotf(self.center.x - otherPoint.center.x, self.center.y - otherPoint.center.y);
}
#end
And then you can create an array of them like so:
NSArray *objectCoords = #[[ChartPoint chartPointWithCenter:CGPointMake(20,80) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(120,60) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(60,84) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(80,88) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(100,93) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(20,20) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(160,70) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(128,68) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(90,60) radius:5],
[ChartPoint chartPointWithCenter:CGPointMake(110,80) radius:5]];
But this
avoids inefficient CGRectFromString;
avoids needing to do those repeated CGRectGetMidX and CGRectGetMidY calls to determine the center of the CGRect; and, most importantly,
more accurately represents what your objects really are.
Obviously, when you want to draw your points, instead of doing:
NSString *string = objectCoords[idx];
CGRect *rect = CGRectFromString(string);
CGContextAddEllipseInRect(context, rect);
You'd do:
ChartPoint *point = objectCoords[idx];
CGContextAddArc(context, point.center.x, point.center.y, point.radius, 0, M_PI * 2.0, YES);

Draw the Path of a Moving SKSpritenode

I am Trying to use the new SKSpritenode , i have managed to create a Spritenode move it around the screen , although i want the Spritenode leaves a trace( color ) where it moves.
the code to create the Sprite node & my attempts to create a shapenode as a child for the spritenode ( which did not work ) .
-(void)movetherayoflight{
ray1=[[lightray1 alloc]initWithColor:[SKColor redColor] size:CGSizeMake(6,6)];
[ray1 setPosition:CGPointMake(50, 50)];
ray1.physicsBody=[SKPhysicsBody bodyWithRectangleOfSize:ray1.size];
ray1.physicsBody.restitution=1;
ray1.physicsBody.linearDamping=0;
ray1.physicsBody.friction=0;
ray1.physicsBody.allowsRotation=NO;
ray1.physicsBody.velocity=CGVectorMake(0.0f, 300.0f);
[self addChild:ray1];
SKShapeNode *raayyy=[[SKShapeNode alloc]init];
CGMutablePathRef rayPath = CGPathCreateMutable();
CGPoint fff=CGPointMake(ray1.position.x, ray1.position.y);
CGPoint startpoint=CGPointMake(50, 100);
//CGPathAddLines(rayPath, NULL, &fff, 2);
CGPathAddLines(rayPath, NULL, &startpoint, 5);
//CGPathAddLineToPoint(rayPath, NULL, ray1.position.x, ray1.position.y);
raayyy.path=rayPath;
raayyy.lineWidth = 1.0;
//raayyy.fillColor = [SKColor whiteColor];
raayyy.strokeColor = [SKColor greenColor];
raayyy.glowWidth = 0.5;
[ray1 addChild:raayyy];
}
If you have better Solution , Please let me know !
Instead of making the SKShapeNode as the child of the SKSpriteNode, declare it as its sibling in the SKScene.
First, declare the SKShapeNode and the CGPath as instance variables.
In your scene's -initWithSize: method,
raayyy=[[SKShapeNode alloc]init];
//Additional initialization here.
rayPath = CGPathCreateMutable();
CGPathMoveToPoint(pathToDraw, NULL, ray1.position.x, ray1.position.y);
rayyy.path = rayPath;
[self addChild:rayyy];
Then in your -update: method,
CGPathAddLineToPoint(rayPath, NULL, yar1.position.x, ray1.position.y);
rayyy.path = rayPath;
This is just a suggestion, I haven't tried anything of the like myself.
well , I have managed it with a solution but i really dont like it so far
-(SKShapeNode*)gravityline{
SKShapeNode *lolo = [[SKShapeNode alloc] init];
CGPoint fff=CGPointMake(ray1.position.x, ray1.position.y);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, fff.x, fff.y);
CGPathAddLineToPoint(path, 0,rayoriginpoint.x,rayoriginpoint.y );
CGPathCloseSubpath(path);
lolo.path = path;
lolo.name=#"gravityline";
lolo.strokeColor=[SKColor greenColor];
lolo.glowWidth=.1;
lolo.physicsBody=[SKPhysicsBody bodyWithPolygonFromPath:path];
lolo.physicsBody.categoryBitMask=raylightCategory;
lolo.physicsBody.collisionBitMask=batCategory;
lolo.physicsBody.contactTestBitMask=batCategory;
lolo.physicsBody.dynamic=NO;
CGPathRelease(path);
return lolo;
}
The rayoriginpointchanges its value once the ray point hits something
if (firstBody.categoryBitMask == rayCategory && secondBody.categoryBitMask==mirrorCategory )
{
[self addChild:[self gravityline]];
CGPoint p = contact.contactPoint;
rayoriginpoint=p;
NSLog(#"Contact have been made");
}
What i want to do is very basic :
1- Either to change the Color of every CGPOINT that the SKSpritenode Passes by
2- Either to create Sprite node that scales up in certain direction till it hits something then changes direction

Transparency/Alpha and MKOverlayView issue

Having wrestled with many problems with MKOverlayViews and MKMapKit I find myself with one more. I'm using an MKOverlayPathView subclass to draw a singular large overlay on the screen (it covers London at the moment). Within that overlay are lots of coloured squares for arguments sake:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
int size = 0;
min = 0;
max = 0;
CGFloat alpha = 0.4f;
int recursionLevel = 4;
QuadNodeOverlay *quadNodeOverlay = (QuadNodeOverlay *)self.overlay;
Cluster *clusters = quadtree_clusters(quadNodeOverlay.quadTree, recursionLevel, &size);
if (clusteringMethod == 1) {
/* Draw paths/squares */
CGPathRef *paths = [self createPaths:clusters size:size];
for (int i = 0; i < size; i++) {
CGPathRef path = paths[i];
Cluster cluster = clusters[i];
CGContextBeginPath(context);
CGContextAddPath(context, path);
//CGContextSetFillColorWithColor(context, colors[cluster.depth]);
CGColorRef gradientColor = [self newColor:cluster.count];
CGContextSetFillColorWithColor(context, gradientColor);
CGContextDrawPath(context, kCGPathFillStroke);
CGColorRelease(gradientColor);
CGPathRelease(path);
}
free(paths);
} else if (clusteringMethod == 2) {
// CUT
}
[self setAlpha:alpha];
}
The above code is incomplete but provides a solid basis for the question. The code works fine and performs admirable fast regardless of the number of squares I want to draw. The problem I have is below a certain point I'm not drawing this overlay; I manage this in the view controller containing the map:
- (void)mapView:(MKMapView *)map regionDidChangeAnimated:(BOOL)animated {
self.annotations = [locationServer itemsForMapRegion:map.region withMapView:map maxCount:100];
NSLog(#"There are like %d cards dude.",[self.annotations count]);
if ( [self.annotations count] > 40) {
[mapView removeAnnotations:annotations];
// Cluster
if (![[mapView overlays] containsObject:quadClusters]) {
[mapView addOverlay:quadClusters];
}
} else {
// Don't
[mapView removeOverlay:quadClusters];
// Add pins.
[mapView removeAnnotations:annotations];
[mapView addAnnotations:annotations];
}
}
Again this code works fine my purposes right now. The problem is when I zoom in to a level where the clusters are not displayed but the individual annotations are when I zoom back out a chuck of the overlay is rendered without transparency. Vis:
As a foot note. Removing the setAlpha from the overlay and modifying the code that draws the squares to read:
CGColorRef gradientColor = [self newColor:cluster.count];
CGColorRef alphaGradientColor = CGColorCreateCopyWithAlpha(gradientColor, alpha);
CGContextSetFillColorWithColor(context, alphaGradientColor);
resolves the issue, however surely applying the alpha channel to the overlay as a whole is more efficient.

MKMapView - Redrawing overlays in the most efficient way

I have an MKMapView with 2 overlays. They represent 1. The route someone has taken. 2. A series of circular regions of interest. In order to update either of the overlays I update their data, then invalidate their related view:
[(RoutePolyline *)self.overlay appendPolylines:polylines];
MKOverlayPathView *overlayView = (MKOverlayPathView *)[self.mapView viewForOverlay:self.overlay];
[overlayView invalidatePath];
The problem is that adding a single line to my RoutePolyline and invalidating its related view causes every overlay view to be redrawn about 80 times. Given that this happens for every location update this is incredibly expensive.
Here is the code from the only method in my RouteOverlayView:
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
RoutePolyline *routePolyline = (RoutePolyline *)self.overlay;
int polylineCount = [routePolyline.polylines count];
for (int i = 0; i < polylineCount; i++)
{
MKPolyline *polyline = [routePolyline.polylines objectAtIndex:i];
CGPathRef path = [MKUtils newPolyPathWithPolyline:polyline overlayView:self];
if (path)
{
[self applyFillPropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathStroke);
[self applyStrokePropertiesToContext:context atZoomScale:zoomScale];
CGContextBeginPath(context);
CGContextAddPath(context, path);
CGContextStrokePath(context);
CGPathRelease(path);
}
}
}
What could be causing these extra redraws?

How to implement undo in a drawing app

Below is the code snippet of painting. I can do undo in a vector drawing just like storing points and remove the highest one from mutable array then redrawing.However, it does not function properly in a raster drawing.
If I use UIGraphicsGetCurrentContext() as a context reference, undo works well. But the context of CGBitmapContextCreate() does not when issue undo action.
- (id)initWithFrame:(CGRect)frame {
objArray = [[NSMutableArray alloc] init];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
canvas = CGBitmapContextCreate(NULL, drawImage.frame.size.width, drawImage.frame.size.height, 8, 0, colorSpace, kCGImageAlphaPremultipliedLast);
CGColorSpaceRelease(colorSpace);
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGImageRef imgRef = CGBitmapContextCreateImage(canvas);
CGRect r = self.bounds;
CGContextDrawImage(context, CGRectMake(0, 0, r.size.width, r.size.height), imgRef);
if(ok) {
for (int i = 0; i < [objArray count]; i++) {
CGPoint point = [[[objArray objectAtIndex: i] objectAtIndex:0] CGPointValue];
CGContextMoveToPoint(canvas, point.x, point.y);
for (int j = 0; j < [[objArray objectAtIndex:i] count]; j++) {
point = [[[objArray objectAtIndex: i] objectAtIndex:j] CGPointValue];
CGContextAddLineToPoint(canvas, point.x, point.y);
CGContextStrokePath(**canvas**);
CGContextMoveToPoint(**canvas**, point.x, point.y);
}
}
}
CGImageRelease(imgRef);
}
- (void)undo:(id) sender {
NSLog(#"click");
if([objArray count] > 0)
[objArray removeLastObject];
ok = YES;
[self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
NSMutableArray *points = [NSMutableArray array];
UITouch *touch = nil;
if (touchPoint) {
touch = [touches member:touchPoint];
}
end = [touch locationInView:self];
[points addObject:[NSValue valueWithCGPoint:start]];
[points addObject:[NSValue valueWithCGPoint:end]];
[objArray addObject:points];
CGContextMoveToPoint(**canvas**, start.x, start.y);
CGContextAddLineToPoint(**canvas**, end.x, end.y);
CGContextSetLineCap(**canvas**, kCGLineCapRound);
CGContextSetLineWidth(**canvas**, 40.0);
CGContextStrokePath(**canvas**);
start = end;
[self setNeedsDisplay];
}
With raster drawing you're changing the pixels in the canvas each time, there are no objects like there are in a vector drawing.
As a result the only "state" you have is the canvas itself. In order to allow for undo you actually need to save a copy of the canvas before each change. Right before you make the change you'll copy the old bitmap context and then make the change. If the user chooses to undo then you'll just copy the saved context over the normal one. If you want to allow for multiple undos you'll have to save multiple copies.
Obviously this can become memory intensive. Technically you don't actually have to save the whole canvas, just the part that has changes on it, with a record of the position of the changed section. If changes are small then you'll save quite a bit of memory, but some changes can affect the whole canvas, not saving anything.
You could potentially save even more memory with algorithms that store changed pixels, but the processing overhead isn't likely worth it.
Assumming you're storing the image in an Image object, create a stack:
Stack undoStack = ...
Stack redoStack = ...
The high memory solution
As the user makes changes you to the image, you can store the next image (w the changes), and the next and the next and so on. When the user wants to undo, you restore the images by popping from the undoStack and pushing onto the redo stack:
void undo(){
redoStack.push(undoStack.pop());
}
To redo, use the same process, but backwards.
The low memory solution
The concent is the same as above, but now instead of storing the whole image, you can XOR the modified image with the previous one (or with the original one) and store only the pixels that have changed and coordinates at which these changes occur. You might even consider quad tree packing of this new XORed image to save memory if the changes are not great.