CALayer Audio Indicator with Mask - objective-c

Ok, What I want to do is create an audio indicator, basically overlay a mask or layer onto an image with a background color and opacity... so it looks like a red level indicator is bouncing up and down overtop of a microphone image, I got this to work in a very poor way updating the image each time with a UIImage mask but this was very inefficient.
Im trying to get it to work now with a CALayer which it does and better than the first trial and error way I tried. The problem now is Im only showing a rectangle and the corresponding level with it. I want it to be bounded by the microphone image, so it looks half full for instance, when I mask to bounds the rectangle takes the shape of the microphone and jumps up and down in that shape instead of "filling" the image.
Hopefully this isn't too confusing, I hope you can understand the premise and help!! Here is some code I have working now, in the wrong way:
self.image = [UIImage imageNamed:#"img_icon_microphone.png"];
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0.f, 0.f, 200.f, 200.f);
maskLayer.contents = (id) [UIImage imageNamed:#"img_icon_microphone.png"].CGImage;
micUpdateLayer = [CALayer layer];
micUpdateLayer.frame = CGRectMake(0.f, 200.f, 200.f, -5.f);
micUpdateLayer.backgroundColor = [UIColor redColor].CGColor;
micUpdateLayer.opacity = 0.5f;
[self.layer addSublayer:micUpdateLayer];
Im then just using a NSTimer and a call to a function which simply updates the micUpdateLayer.frame y to make it appear to be moving with the audio input.
Thank you for any suggestions!

Related

CAMetalLayer with texture rendered by CARenderer is not visible?

I'm using a CARenderer to render another CALayer tree into a CAMetalLayer, which I hope to use as the mask of yet another layer. For testing purposes, I've tried adding the CAMetalLayer as a normal sublayer instead of a mask.
The layer object below is not visible after adding it to a superlayer that is definitely visible. I've confirmed the frame of the layer is not a problem. Here's how I'm making the CAMetalLayer and its CARenderer.
CAMetalLayer *layer = [CAMetalLayer layer];
layer.frame = bounds;
layer.device = MTLCreateSystemDefaultDevice();
//layer.opaque = NO;
//layer.framebufferOnly = NO;
id<CAMetalDrawable> drawable = layer.nextDrawable;
_lastDrawable = drawable;
_renderer = [CARenderer rendererWithMTLTexture:drawable.texture options:nil];
_renderer.layer = self.superview.layer;
_renderer.bounds = bounds;
👇 By creating a CIImage and inspecting it with the debugger, I've confirmed the CARenderer is updating the Metal texture.
CIImage *img = [CIImage imageWithMTLTexture:_lastDrawable.texture options:nil];
But when I set the superlayer of the CAMetalLayer, it's nowhere to be seen.
[self.layer addSublayer:layer];
Here's how I'm using the CARenderer:
[_renderer beginFrameAtTime:CACurrentMediaTime() timeStamp:NULL];
[_renderer addUpdateRect:bounds];
[_renderer render];
[_renderer endFrame];
That last snippet runs frequently.
edit 1
I've added a backgroundColor and now the layer is visible, but its texture is not being rendered inside it.
layer.backgroundColor = NSColor.yellowColor.CGColor;
I would recommend just setting the original layer as a mask rather than trying to render it to a texture first; you’re sort of duplicating the work that CA would be doing anyway.
If you really need control over when the mask layer tree gets rendered—and again you should definitely try the standard method first—the right way to do this would be to create an IOSurface-backed MTLTexture rather than using a CAMetalLayer’s drawable, draw into the texture with your CARenderer, set the IOSurface as the contents of a regular CALayer, and use that layer as the mask.

IOS::How we can implement this [Curved Progress Bar]

I need to implement this functionality.Please suggest me.
It's not working properly means it is taking the end angle for the filling colour but here mentioned the "fromValue" and "toValue" but its not going through the fromValue and toValue.
Please anyone can edit my code.
Thanks in advance.
CAShapeLayer *circle=[CAShapeLayer layer];
circle.path=[UIBezierPath bezierPathWithArcCenter:CGPointMake(self.img_View.frame.origin.x, self.img_View.frame.origin.y) radius:50 startAngle:0 endAngle:90 clockwise:YES].CGPath;
circle.fillColor=[UIColor clearColor].CGColor;
circle.strokeColor=[UIColor greenColor].CGColor;
circle.lineWidth=16;
CABasicAnimation *animation=[CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.duration=10;
animation.removedOnCompletion=NO;
// animation.fromValue=#(0);
animation.fromValue=[NSNumber numberWithInt:0];
animation.toValue=[NSNumber numberWithInt:20];
animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[circle addAnimation:animation forKey:#"drawCircleAnimation"];
[img_View.layer.sublayers makeObjectsPerformSelector:#selector(removeFromSuperlayer)];
[img_View.layer addSublayer:circle];
you can do it with UIBezierPath it is very efficient to draw shapes.
The bezier path you use as a clip seems to be just a fraction of a circle, while in the image you show, the path is more complex : 2 fractions of a circle, linked by 2 lines, the whole path having a 'ring' shape.
This approach should work, I used it for a timer with the same kind of look. Although I didn't used directly AngleGradientLayer, I modified its - (CGImageRef)newImageGradientInRect:(CGRect)rect method to return a UIImage. But I had to rotate this image by + PI/2, as Pavlov gradient angular gradient starts horizontally.
I use a UIImage, because it's a background that DOESN'T change, so I saved an instance of this UIImage in my layer, and draw it whenever I update the clipping path
- (void)drawInContext:(CGContextRef)ctx {
UIBezierPath *currentPath = [self timerPath];
// other drawing code for glow (shadow) and white stroke)
CGContextAddPath(ctx, currentPath.CGPath);
// clip !
CGContextClip(ctx);
CGContextDrawImage(ctx, self.bounds, _circularGradientImage.CGImage);
//_circularGradientImage from modified newImageGradientInRect method.
}

CALayer Position Contents to Bottom Left

I am attempting to draw an image onto a CALayer. I only need an image, so I have created my later as follows:
CALayer *layer = [CALayer layer];
I add my image as follows:
NSImage *img = [[NSImage alloc] initWithContentsOfFile:#"path/to/img.png"];
[layer setContents:img];
This works, however it draws my images to fill the entire parent frame (stretching my image in the process).
Reading the docs, I found the following:
[layer setContentsGravity:#"kCAGravityBottomLeft"];
I am attempting to draw my image in the bottom left of the parent frame, however no matter what I do it draws my icon in the bottom center. Is there anyway to specify the bottom left?
Try this:
CALayer *layer = [CALayer layer];
layer.contentsGravity = kCAGravityBottomLeft;
layer.contents = (id) aCGImageRef;
Note that kCAGravityBottomLeft is declared as NSString and the contents property expects a CGImageRef.

ios: Screenshot doesn't display mask Layer

I have a view with a certain background color. I am masking this view with the following code:
UIView *colorableView = [[UIView* alloc] init];
colorableView.backgroundColor = someColor;
CALayer *maskLayer = [CALayer layer];
maskLayer.contents = (id)[UIImage imageNamed:maskImageName].CGImage;
colorableView.layer.mask = maskLayer;
Ok everything works fine there. The view gets masked, so some parts are transparent. Now I make a screenshot of this view:
CGRect frame = colorableView.frame;
UIGraphicsBeginImageContext(frame.size);
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(someUninterestingCodeToGetACorrectPosition);
[self.view.layer renderInContext:c];
UIImage *screenShotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenShotImage;
Taking a screenshot works (actually I display some other stuff above the view too and that gets displayed in the screenshot as well), but somehow, the mask is not recognized. Meaning what I get is a screenshot of a fully colored view (a rectangle) without the mask hiding some parts of it.
I guess ´UIGraphicsGetImageFromCurrentImageContext()`doesn't work with mask layers, so what can I do about it? I need to have a UIImage to display the screenshot in a mail.
Thanks a lot in advance
One way to fix this can be to use Quartz functions to clip the view (CGContextClip, I don't remember exactly, you'll have to dig a little bit into the documentation).
Hope this will help

Mirroring CIImage/NSImage

Currently I have the following
CIImage *img = [CIImage imageWithCVImageBuffer: imageBuffer];
NSCIImageRep *imageRep = [NSCIImageRep imageRepWithCIImage:img];
NSImage *image = [[[NSImage alloc] initWithSize: [imageRep size]] autorelease];
[image addRepresentation:imageRep];
This works perfectly, I can use the NSImage and when written to a file the image is exactly how I need it to be.
However, I'm pulling this image from the users iSight using QTKit, so I need to be able to flip this image across the y axis.
My first thought was to transform the CIImage using something like this, however my final image always comes out completely blank. When written to a file the dimensions are correct but it's seemingly empty.
- (CIImage *)flipImage:(CIImage *)image
{
return [image imageByApplyingTransform:CGAffineTransformMakeScale(-1, 1)];
}
Am I approaching this the wrong way? Or have I made a mistake in my code?
That transform flips it, but the axis around which it flips is not at the center of the image, but at the left edge. You must also translate the image by its width to account for the movement caused during the scale.
Here is some code that may help someone out ==>
CGAffineTransform rotTrans = CGAffineTransformMakeRotation(M_PI_2);
CGAffineTransform transTrans1 = CGAffineTransformTranslate(rotTrans, 0.0f, 320.0f);
CGAffineTransform scaleTrans = CGAffineTransformScale(transTrans1, 1.0, -1.0);
CGAffineTransform transTrans2 = CGAffineTransformTranslate(scaleTrans, -0.0f, -320.0f);
self.view.transform = transTrans2;
I use it to flip frames from the front camera horizontally so they always appear up no matter what the rotation of the device. This stuff does get kind of tricky. One thing to do to help figure out what is going on is scaling down along either of the axes and seeing what the result is.