Selection of Irregular part of an Image ios - ios

I have an image shown below:
In which i have to select all parts one by one and change their colour, I have tried many methods but couldn't able to solve this. I have tried Bezier Path but no success.
This is the code am using currently for selection of any part based on colour, and then i have changed that colour to red as shown below:
But the problem is I have to change colour of only that part of image which i have selected not all which have same colour.
Any idea how to solve this.
- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
UITouch* touch = [touches anyObject];
CGPoint point = [touch locationInView:_bodyImgView]; //where image was tapped
NSLog(#"X = %f \n Y= %f",point.x,point.y);
pickedColor = [self.view colorOfPoint:point image:_bodyImgView.image];
NSString *col = [NSString stringWithFormat:#"%#",pickedColor];
NSLog(#"color %#",pickedColor);
if (![col isEqualToString:#"UIExtendedSRGBColorSpace 0 0 0 0"]) {
UIImage *img = [self.view replaceColor:pickedColor inImage:_bodyImgView.image
withTolerance:25.0];
[_bodyImgView setImage:img];
}
}
colorOfPoint:image is a method to extract colour from picked point.
replaceColor:inImage:withTolerance: is the method to replace the picked color.

Do you need to be able to cope with any image, or is this a fixed image or one of a few you can preprocess? Ideally, you'd create several images, each containing one selectable region only (and the rest transparent). Then it's a matter of just looping over the images and finding one where the corresponding point is opaque, and filling the image in a blend mode that preserves transparency.
If that is not an option, maybe you could perform a seed fill to generate a mask (start searching from the click position and take any color until you hit a white pixel), then apply that mask and then perform the blend mode painting, only touching the white pixels.
Alternately, if you're manually walking the picture to seed fill already, you could look at each pixel and manually apply the color to it, maybe by determining how much blue it contains and then generating a pixel that has that amount of blue instead.

Related

Is it possible to add a transparent layer or view above a colored view?

I'm using AVFoundation framework to scan a barcode, but that may be unrelevant for my problem.
What I want:
I would like that the square bordered in green be transparent (not with the darkened black).
Here is what I have done:
I have 2 views: backgroundView( which occupies the whole screen) and highlightView which is the square bordered with green, on top of backgroundView (I have used a XIB for dimensions and positions) :
self.highlightView.layer.borderColor = [UIColor greenColor].CGColor;
self.highlightView.layer.borderWidth = 3;
// this following line does not allow the square to be transparent
self.highlightView.layer.opacity = 0;
// relative to AVFoundation
_previewLayer.frame = _backgroundView.bounds;
[_backgroundView.layer addSublayer:_previewLayer];
_previewLayer.backgroundColor = [UIColor blackColor].CGColor;
_previewLayer.opacity = 0.3;
UPDATE : xib (here representing the square with a clear color background), the backgroundView has the property black color background).
As I mentioned, you were looking in the wrong direction. There are multiple posts with a problem similar to yours, which have pretty decent answers. (You will have to study and understand to make the most of them):
Cut Out Shape with Animation
Simply mask a UIView with a rectangle
To sum it up, you need to apply the semi-transparent color to the layer of backgroundView and then play around with the layer's mask property to get the work done.
You can find many tutorials to learn using the layer and mask together.
Hope this helps.

How do i rotate an image inside a drawn polygon

I am a beginner programmer and this is my first app(I am still learning). I have overlaid a polygon onto a map view. I have set its fill color to an image because I'm trying to match an image to a satellite picture. I want to rotate it so that the polygon contents match the map. Is it possible to rotate the image? If not, is there an easier way to overlay an image onto a map view that I could use.
Here is my code:
-(MKOverlayView*)mapView:(MKMapView *)mapView viewForOverlay:(id )overlay {
MKPolygonView *polyView = [[MKPolygonView alloc] initWithOverlay:overlay];
polyView.strokeColor = [UIColor whiteColor];
polyView.fillColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"Campus-map labels.jpg"]];
return polyView;
}
Here's what I'm trying to do, if it helps:
http://i.stack.imgur.com/x53HU.jpg
The road which is circled in red should match up. I know that the polygon isn't in the right position -- this is to illustrate how the polygon needs to be rotated.
You can modify the transform property of the polyView object. For example:
polyView.transform = CGAffineTransformMakeRotation(M_PI_4);
will rotate the polygon by pi/4 radians (45 degrees), in a clockwise direction.
You might need to change the polygon's center property to get the effect you want. The center property determines the center of rotation around which the transform rotation takes place.

How to create an alpha mask in iOS using sprite kit

The effect that I am trying to achieve is having a circle of light in an area of darkness. The effect is similar to that in pokemon games, when you are in a dark cave and only have limited vision surrounding you. From what I have tried and read, I have been unable to create a mask over nodes in sprite kit that has alpha levels. The masks I manage to create all have a hard edge, and basically just crop. Reading on the apple developer page about the SKCropNode, which has the maskNode property, it says "If the pixel in the mask has an alpha value of less than 0.05, the image pixel is masked out." This unfortunately sounds to me like the pixels will either be completely masked out or completely included, with no alpha values in between. If what I am trying to say has been hard to follow, here is an image of what I have achieved:
https://www.dropbox.com/s/y5gbk8qvuq4ynh0/iOS%20Simulator%20Screen%20shot%20Jan%2020%2C%202014%201.06.23%20PM.png
and here is an image of what I would like to achieve:
https://www.dropbox.com/s/wtwfdi1mjs2n8e6/iOS%20Simulator%20Screen%20shot%20Jan%2020%2C%202014%201.05.54%20PM.png
The way that I managed to get the above result, was I masked out the hard edge circle and then just added an image that has a gradient going from black on the outside to transparent on the inside. The reason this approach doesn't work is because I need to have multiple circles, and with the method I just mentioned, when the circles intersect the darkness on the outside of the transparent circle can be seen.
In conclusion, what I need is a way to have a circle that starts dark in the center, and then fades out. Then, have it so where the circle is dark, the image behind it can be seen, and where the circle is transparent, the image behind it cannot be seen. Again, sorry if what I am saying is difficult to follow. Here is the code I am using. Some of it was found from other posts.
SKSpriteNode *background = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(500, 500)];
background.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
SKCropNode *cropNode = [[SKCropNode alloc] init];
SKNode *area = [[SKNode alloc] init];
int x = 65; //radius of the circle
_circleMask = [[SKShapeNode alloc ]init];
CGMutablePathRef circle = CGPathCreateMutable();
CGPathAddArc(circle, NULL, 0, 0, x/2, 0, M_PI*2, YES);
_circleMask.path = circle;
_circleMask.lineWidth = x*2;
_circleMask.strokeColor = [SKColor whiteColor];
_circleMask.name=#"circleMask";
_circleMask.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
//Here is where I just added in the gradient circle To give the desired appearance, but this isn't necessary to the code
//_circleDark = [SKSpriteNode spriteNodeWithImageNamed:#"GradientCircle"];
//_circleDark.position = [cropNode convertPoint:_circleMask.position fromNode:area];
[area addChild:_circleMask];
[cropNode setMaskNode:area];
[cropNode addChild:background];
//[cropNode addChild:_circleDark];
[self addChild:cropNode];
This method has also allowed me to move the circles around, revealing different parts of the image behind it, which is what I want. To do this I just set it to change the _circleMask.position when the user taps the screen.
Also, just to make this clear in case anyone was confused, the black is just the background color of the scene, the picture is on top of that, and then the circle is part of the mask node.
A very simple (and maybe less... or more performant) version of this would be to simply add a SKSpriteNode on top which has your vignette on a transparent background. In other words, if viewed in Photoshop, you would see a decreasing amount of checkerboard visible in the circle as you go from the center out, eventually displaying solid black. When the PNG image is used in your app, this transparency will be preserved when the two sprites are composited.
I have an idea... I hope it would help.
Make a PNG with the gradient you want from white to black with no transparency.
Use a separate sprite node with the png for each light you want and add them all to a SKEffectNode or SKCropNode node. It doesn't matter which since they are both rendered in a separate context. Set each sprite node to screen blending mode.
Then, when adding the parent SKEffectNode or SKCropNode to the scene, set it to multiply blend mode.
In the end, the screening will merge the "lights" together nicely, while the multiply will make the white area transparent.

Multiple Polygon overlays from array

I have an array with polygons created from a data file with coordinates per polygon.
So when I plot them on my map I use:
[mapView addOverlays:polygonArray];
and in my viewForOverlay:
if ([overlay isKindOfClass:[MKPolygon class]]) {
MKPolygonView *polyView = [[MKPolygonView alloc] initWithPolygon:overlay];
polyView.fillColor = [[UIColor redColor] colorWithAlphaComponent:0.1];
polyView.strokeColor = [[UIColor blueColor] colorWithAlphaComponent:0.1];
polyView.lineWidth = 1;
return polyView;
}
else {
return nil;
}
The problem is that regarding my "colorWithAlphaComponent" the code seems to reuse and recreate the polyView for each Polygon. Therefore the first one is with alpha 0.1 but the second is 2x and so on.. So the last few Polygons aren't "seethrough" anymore.
Here's how it looks:
Based on the problem description and picture, it sounds like you are adding the same polygon multiple times so it gets overlapped with itself.
When overlays overlap, the map view blends their colors together resulting in a darker appearance.
If polygonArray contains unique polygons itself but addOverlays is called multiple times, you should call removeOverlays before addOverlays if the existing polygons on the map are already included in polygonArray.
Another possibility is that polygonArray itself contains duplicate polygons.
Even if addOverlays is called only once, the map will add multiple instances of the same polygon resulting in those overlays overlapping themselves giving them a darker color than expected.
To fix this, you should eliminate the duplication in polygonArray.

iOS CAKeyframeAnimation rotationMode on UIImageView

I am experimenting with Key Frame animation of the position of a UIImageView object moving along a bezier path. This pic shows the initial state before animation. The blue line is the path - initially moving straight up, the light green box is the initial bounding box or the image, and the dark green "ghost" is the image that I am moving:
When I kick off the animation with rotationMode set to nil, the image keeps the same orientation all the way through the path as expected.
But when I kick off the animation with rotationMode set to kCAAnimationRotateAuto, the image immediately rotates 90 degrees anti-clockwise and keeps this orientation all the way through the path. When it reaches the end of the path it redraws in the correct orientation (well it actually shows the UIImageView that I repositioned in the final location)
I was naively expecting that the rotationMode would orientate the image to the tangent of the path and not to the normal, especially when the Apple docs for the CAKeyframeAnimation rotationMode state
Determines whether objects animating along the path rotate to match the path tangent.
So what is the solution here? Do I have to pre-rotate the image by 90 degrees clockwise? Or is there something that I am missing?
Thanks for your help.
Edit 2nd March
I added a rotation step before the path animation using an Affine rotation like:
theImage.transform = CGAffineTransformRotate(theImage.transform,90.0*M_PI/180);
and then after the path animation, resetting the rotation with:
theImage.transform = CGAffineTransformIdentity;
This makes the image follow the path in the expected manner. However I am now running into a different problem of the image flickering. I am already looking for a solution to the flickering issue in this SO question:
iOS CAKeyFrameAnimation scaling flickers at animation end
So now I don't know if I have made things better or worse!
Edit March 12
While Caleb pointed out that yes, I did have to pre rotate my image, Rob provided an awesome
package of code that almost completely solved my problems. The only thing that Rob didn't do was compensating for my assets being drawn with a vertical rather than horizontal orientation, thus still requiring to preRotate them by 90 degrees before doing the animation. But hey, its only fair that I have to do some of the work to get things running.
So my slight changes to Rob's solution to suite my requirements are:
When I add the UIView, I pre Rotate it to counter the inherent rotation added by setting the rotationMode:
theImage.transform = CGAffineTransformMakeRotation(90*M_PI/180.0);
I need to keep that rotation at the end of the animation, so instead of just blasting the view's transform with a new scale factor after the completion block is defined, I build the scale based on the current transform:
theImage.transform = CGAffineTransformScale(theImage.transform, scaleFactor, scaleFactor);
And that's all I had to do to get my image to follow the path as I expected!
Edit March 22
I have just uploaded to GitHub a demo project that shows off the moving of an object along a bezier path. The code can be found at PathMove
I also wrote about it in my blog at Moving objects along a bezier path in iOS
The issue here is that Core Animation's autorotation keeps the horizontal axis of the view parallel to the path's tangent. That's just how it works.
If you want your view's vertical axis to follow the path's tangent instead, rotating the contents of the view as you're currently doing is the reasonable thing to do.
Here's what you need to know to eliminate the flicker:
As Caleb sort of pointed out, Core Animation rotates your layer so that its positive X axis lies along the tangent of your path. You need to make your image's “natural” orientation work with that. So, supposing that's a green spaceship in your example images, you need the spaceship to point to the right when it doesn't have rotation applied to it.
Setting a transform that includes rotation interferes with the rotation applied by `kCAAnimationRotateAuto'. You need to remove the rotation from your transform before applying the animation.
Of course that means you need to reapply the transformation when the animation completes. And of course you want to do that without seeing any flicker in the appearance of the image. That's not hard, but there some secret sauce involved, which I explain below.
You presumably want your spaceship to start out pointing along the tangent of the path, even when the spaceship is sitting still having not been animated yet. If your spaceship image is pointing to the right, but your path goes up, then you need to set the transform of the image to include a 90° rotation. But perhaps you don't want to hardcode that rotation - instead you want to look at the path and figure out its starting tangent.
I'll show some of the important code here. You can find my test project on github. You may find some use in downloading it and trying it out. Just tap on the green “spaceship” to see the animation.
So, in my test project, I have connected my UIImageView to an action named animate:. When you touch it, the image moves along half of a figure 8 and doubles in size. When you touch it again, the image moves along the other half of the figure 8 (back to the starting position), and returns to its original size. Both animations use kCAAnimationRotateAuto, so the image points along the tangent of the path.
Here's the start of animate:, where I figure out what path, scale, and destination point the image should end up at:
- (IBAction)animate:(id)sender {
UIImageView* theImage = self.imageView;
UIBezierPath *path = _isReset ? _path0 : _path1;
CGFloat newScale = 3 - _currentScale;
CGPoint destination = [path currentPoint];
So, the first thing I need to do is remove any rotation from the image's transform, since as I mentioned, it will interfere with kCAAnimationRotateAuto:
// Strip off the image's rotation, because it interferes with `kCAAnimationRotateAuto`.
theImage.transform = CGAffineTransformMakeScale(_currentScale, _currentScale);
Next, I go into a UIView animation block so that the system will apply animations to the image view:
[UIView animateWithDuration:3 animations:^{
I create the keyframe animation for the position and set a couple of its properties:
// Prepare my own keypath animation for the layer position.
// The layer position is the same as the view center.
CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
positionAnimation.path = path.CGPath;
positionAnimation.rotationMode = kCAAnimationRotateAuto;
Next is the secret sauce for preventing flicker at the end of the animation. Recall that animations do not effect the properties of the “model layer“ that you attach them to (theImage.layer in this case). Instead, they update the properties of the “presentation layer“, which reflects what's actually on the screen.
So first I set removedOnCompletion to NO for the keyframe animation. This means the animation will stay attached to the model layer when the animation is complete, which means I can access the presentation layer. I get the transform from the presentation layer, remove the animation, and apply the transform to the model layer. Since this is all happening on the main thread, these property changes all happen in one screen refresh cycle, so there's no flicker.
positionAnimation.removedOnCompletion = NO;
[CATransaction setCompletionBlock:^{
CGAffineTransform finalTransform = [theImage.layer.presentationLayer affineTransform];
[theImage.layer removeAnimationForKey:positionAnimation.keyPath];
theImage.transform = finalTransform;
}];
Now that I've set up the completion block, I can actually change the view properties. The system will automatically attach animations to the layer when I do this.
// UIView will add animations for both of these changes.
theImage.transform = CGAffineTransformMakeScale(newScale, newScale);
theImage.center = destination;
I copy some key properties from the automatically-added position animation to my keyframe animation:
// Copy properties from UIView's animation.
CAAnimation *autoAnimation = [theImage.layer animationForKey:positionAnimation.keyPath];
positionAnimation.duration = autoAnimation.duration;
positionAnimation.fillMode = autoAnimation.fillMode;
and finally I replace the automatically-added position animation with the keyframe animation:
// Replace UIView's animation with my animation.
[theImage.layer addAnimation:positionAnimation forKey:positionAnimation.keyPath];
}];
Double-finally I update my instance variables to reflect the change to the image view:
_currentScale = newScale;
_isReset = !_isReset;
}
That's it for animating the image view with no flicker.
And now, as Steve Jobs would say, One Last Thing. When I load the view, I need to set the transform of the image view so that it's rotated to point along the tangent of the first path that I will use to animate it. I do that in a method named reset:
- (void)reset {
self.imageView.center = _path1.currentPoint;
self.imageView.transform = CGAffineTransformMakeRotation(startRadiansForPath(_path0));
_currentScale = 1;
_isReset = YES;
}
Of course, the tricky bit is hidden in that startRadiansForPath function. It's really not that hard. I use the CGPathApply function to process the elements of the path, picking out the first two points that actually form a subpath, and I compute the angle of the line formed by those two points. (A curved path section is either a quadratic or cubic bezier spline, and those splines have the property that the tangent at the first point of the spline is the line from the first point to the next control point.)
I'm just going to dump the code here without explanation, for posterity:
typedef struct {
CGPoint p0;
CGPoint p1;
CGPoint firstPointOfCurrentSubpath;
CGPoint currentPoint;
BOOL p0p1AreSet : 1;
} PathState;
static inline void updateStateWithMoveElement(PathState *state, CGPathElement const *element) {
state->currentPoint = element->points[0];
state->firstPointOfCurrentSubpath = state->currentPoint;
}
static inline void updateStateWithPoints(PathState *state, CGPoint p1, CGPoint currentPoint) {
if (!state->p0p1AreSet) {
state->p0 = state->currentPoint;
state->p1 = p1;
state->p0p1AreSet = YES;
}
state->currentPoint = currentPoint;
}
static inline void updateStateWithPointsElement(PathState *state, CGPathElement const *element, int newCurrentPointIndex) {
updateStateWithPoints(state, element->points[0], element->points[newCurrentPointIndex]);
}
static void updateStateWithCloseElement(PathState *state, CGPathElement const *element) {
updateStateWithPoints(state, state->firstPointOfCurrentSubpath, state->firstPointOfCurrentSubpath);
}
static void updateState(void *info, CGPathElement const *element) {
PathState *state = info;
switch (element->type) {
case kCGPathElementMoveToPoint: return updateStateWithMoveElement(state, element);
case kCGPathElementAddLineToPoint: return updateStateWithPointsElement(state, element, 0);
case kCGPathElementAddQuadCurveToPoint: return updateStateWithPointsElement(state, element, 1);
case kCGPathElementAddCurveToPoint: return updateStateWithPointsElement(state, element, 2);
case kCGPathElementCloseSubpath: return updateStateWithCloseElement(state, element);
}
}
CGFloat startRadiansForPath(UIBezierPath *path) {
PathState state;
memset(&state, 0, sizeof state);
CGPathApply(path.CGPath, &state, updateState);
return atan2f(state.p1.y - state.p0.y, state.p1.x - state.p0.x);
}
Yow mention that you kick off the animation with "rotationMode set to YES", but the documentation states that rotationMode should be set using an NSString...
In particular:
These constants are used by the rotationMode property.
NSString * const kCAAnimationRotateAuto
NSString * const kCAAnimationRotateAutoReverse
Have you tried setting:
keyframe.animationMode = kCAAnimationRotateAuto;
The documentation states:
kCAAnimationRotateAuto: The objects travel on a tangent to the path.

Resources