iOS 7/8 Objective C - apply blendmode to views - ios

I would like to apply a saturation blendMode using a UIView on other views, like this :
: Example
I have a UILabel and UIImageView in the view of my ViewController, plus a BlendView which overwrites drawRect method.
- (void) drawRect:(CGRect)area
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
[[UIColor whiteColor] setFill];
CGContextSetBlendMode(context, kCGBlendModeSaturation);
CGContextFillRect(context, self.bounds);
CGContextRestoreGState(context);
}
However, I only have a black square if kCGBlendModeSaturation is given and a white square if kCGBlendModeMultiply give - the blendmode isn't applied.
I have set the BlendView as not opaque and tested with different alpha value, no changes.
P.S.: I wish the blendmode to be applied on any views, not only images.

You can't apply a blend mode to a single view and hope it'll have any effect on the views below, like you would with layers in Photoshop (the only compositing you can do between two separate views are change their opacity).
The only thing you can do is, on a single view's drawRect, draw with a specific fill mode over the previous drawing instructions.

Related

Making UIView foreground transparent

I am modifying an IOS app to allow an additional background layer. All the existing overlays are UIImageViews, which handle transparency just fine.
The existing background is the main UIView. I want to change the black background to transparent, to put another view behind. It doesn't work. The simplest code which doesn't work as I would hope is as follows:
# import "TestUIView.h"
#implementation TestUIView
- (void)drawRect:(CGRect)rect {
self.opaque = NO;
[self setBackgroundColor:[UIColor colorWithRed:0 green:0 blue:0 alpha:0]];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0,0,0,0);
CGContextFillRect(context, rect);
// and here goes my code where I draw to the transparent background
}
#end
When this is executed, it produces a black rectangle. I have set its background to transparent and filled its foreground with transparent. What do I have to do to get a UIView where I can draw against a transparent background?
(And I know I can change the alpha of the view as a whole, that's not the issue. I want opaque drawing on a truly transparent background).
UPDATE: Its not in the setbackgroundcolor, I appreciate it isn't necessary and so shouldn't be there, but the same thing happens if it is commented out. I can't draw against a transparent background; the code above shows I can't even make a transparent rectangle to draw onto.
Thanks
By default the value of opaque is NO. Also if you set the alpha to 0, then the view will be completely invisible to the user.If you want to have transparency, then you need to have an alpha value greater than 0 and less than 1.
Also the backgroundColor property is used to set the view’s color rather than drawing that color yourself.
If you need to change the background color in drawRect, you will have to draw it yourself:
self.backgroundColor=yourColorWithSomeAlphaValue;//
[self.backgroundColor setFill];
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);
More explanation is provided in here:

How to improve performance in rendering image?

I am drawing image on a custom UIView. On resizing the view, the drawing performance goes down and it starts lagging.
My image drawing code is below:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIBezierPath *bpath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, width, height)];
CGContextAddPath(context, bpath.CGPath);
CGContextClip(context);
CGContextDrawImage(context, [self bounds], image.CGImage);
}
Is this approach correct?
You would be better using Instruments to find where the bottleneck is than asking on here.
However, what you will probably find is that every time the frame changes slightly the entire view will be redrawn.
If you're just using the drawRect to clip the view into an oval (I guess there's an image behind it or something) then you would be better off using a CAShapeLayer.
Create a CAShapeLayer and give it a CGPath then add it as a clipping layer to the view.layer.
Then you can change the path on the CAShapeLayer and it will update. You'll find (I think) that it performs much better too.
If your height and width are the same, you could just use a UIImageView instead of needing a custom view, and get the circular clipping by setting properties on the image view's layer. That approach draws nice and quickly.
Just set up a UIImageView (called "image" in my example) and then have your view controller do this once:
image.layer.cornerRadius = image.size.width / 2.0;
image.layer.masksToBounds = YES;

Transparent UILabel textColor on superview.superview (sort of)

I would like to achieve the same effect as in the new iTunes remote update for iOS 7 for my UILabel.
If you look at this screen:
(The one on the right) you'll notice that the UILabel text color is the background blurred album cover without the black mask.
At the moment I have transparent UILabel textColor (http://cl.ly/SInK), my code is similar to https://github.com/robinsenior/RSMaskedLabel
My first assumption is to have a view hierarchy like that
UIImageView (Light blurred image)
|
UIImageView/UIView (Dark blurred image or just a dark mask for the superview)
|
UILabel (And all my other view).
I would like the UILabel to have a transparent text color on the first UIImageView, ignoring the second one/Mask).
I can't wrap my head around a solution to achieve this effect.
Since iOS 8 Apple offer a new class, UIVibrancyEffect, which can be added to a UIVisualEffectView. This combined with a UIBlurEffect can achieve the exact same result as my screenshot above.
Source: https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIVibrancyEffect/index.html
I don't think you're going to be able to directly do this with a UILabel. What I did was decompose it into several pieces:
How to draw only the background of a label. For this I cribbed code from CGPathRef from string to turn the string into a path. Then the subclassing UILabel and adding the following drawRect did the appropriate thing.
-(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
// Convert the attributed text to a UIBezierPath
CGPathRef path = [self.attributedText createPath];
// Adjust the path bounds horizontally as requested by the view
CGRect bounds = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines];
CGAffineTransform xform = CGAffineTransformMakeTranslation(bounds.origin.x, bounds.origin.y);
// Adjust the path bounds vertically because it doesn't seem to be taken into account yet
bounds = CGPathGetBoundingBox(path);
xform = CGAffineTransformTranslate(xform, 0., (self.bounds.size.height - bounds.size.height) / 2.);
// Apply the transform to the path
path = CGPathCreateCopyByTransformingPath(path, &xform);
// Set colors, the fill color should be the background color because we're going
// to draw everything BUT the text, the text will be left clear.
CGContextSetFillColorWithColor(ctx, self.textColor.CGColor);
// Flip and offset things
CGContextScaleCTM(ctx, 1., -1.);
CGContextTranslateCTM(ctx, 0., 0. - self.bounds.size.height);
// Invert the path
CGContextAddRect(ctx, self.bounds);
CGContextAddPath(ctx, path);
CGContextDrawPath(ctx, kCGPathEOFill);
// Discard the path
CFRelease(path);
// Restore gstate
CGContextRestoreGState(ctx);
}
If you create a UILabel in interface builder, set the class, set the background color to clear and the foreground color to your proposed fill color, the background will show through where the text is.
To blur the body showing through the label, I dropped an FXBlurView (https://github.com/nicklockwood/FXBlurView) under the label with sizing constraints to keep them aligned.
You can see it all working here https://github.com/dwb357/TransparentLabel.
The color is not uniform. But it's actually "cut out" to let the blurred and tinted background shine through.
One way to do it is to construct a CGPath for the glyphs, clip the graphics context to them, then clear the context, leaving the area outside black and the area inside fhe glyths translucent.
An alternative approach is to create a pattern color from this large blurred tinted cover art and use this pattern color on a UILabel.

How to smooth edges of drawn image?

I'm using this code to colorize some images of a UIButton subclass:
UIImage *img = [self imageForState:controlState];
// begin a new image context, to draw our colored image onto
UIGraphicsBeginImageContextWithOptions(img.size, NO, 0.0f);
// get a reference to that context we created
CGContextRef context = UIGraphicsGetCurrentContext();
// set the fill color
[self.buttonColor setFill];
CGContextSetAllowsAntialiasing(context, true);
CGContextSetShouldAntialias(context, true);
// translate/flip the graphics context (for transforming from CG* coords to UI* coords
CGContextTranslateCTM(context, 0, img.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// set the blend mode to multiply, and the original image
CGContextSetBlendMode(context, kCGBlendModeScreen);
CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
CGContextDrawImage(context, rect, img.CGImage);
// set a mask that matches the shape of the image, then draw the colored image
CGContextClipToMask(context, rect, img.CGImage);
CGContextAddRect(context, rect);
CGContextDrawPath(context,kCGPathFill);
// generate a new UIImage from the graphics context we drew onto
UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//return the colored image
[self setImage:coloredImg forState:controlState];
But the images come out with rough edges. I've tried using screen, lighten, and plusLighter blend modes, because some of the images have white parts that I want to stay white. The only part I want colorized is the black areas. I've attached the original button images, and after they've been colorized. I can't get the edges to look good. When I had them as white images that were colorized using multiply blend mode, it looked much better. But I want to use black so I can use one method for colorizing images with and without white in them. I tried with anti-aliasing, that didn't help either. It looks like it just isn't anti-aliasing it. I haven't worked with Core Graphics enough to know what's up with it.
EDIT
Here's what the original PNGs look like:
and here's what it should look like:
and here's what it does look like:
The size if different, but you can see the bad quality around the edges.
Maybe your original icons (PNGs?) are just "too sharp"? Could you show us? You just draw the image at its original size without resizing, so the problem could be right from the start.
I'm not sure what is what you are trying to accomplish here. Are you trying to round the edges of the images? If so, you are better of by changing the round corner property of the UIButton's layer. Since UIButton is a subclass of UIView, you can get its layer property and change the edge color and round its corner.

iPhone - Draw transparent rectangle on UIView to reveal view beneath

I currently have two UIViews: one of a red background and the other blue. The blue view is a subview of the red view. What I would like to do is be able to "cut" out rectangles on the blue view so that the red view can be visible. How do you go about doing this?
You have to override the top view's drawRect method. So, for example, you might create a HoleyView class that derives from UIView (you can do that by adding a new file to your project, selecting Objective-C subclass, and setting "Subclass of" to UIView). In HoleyView, drawRect would look something like this:
- (void)drawRect:(CGRect)rect {
// Start by filling the area with the blue color
[[UIColor blueColor] setFill];
UIRectFill( rect );
// Assume that there's an ivar somewhere called holeRect of type CGRect
// We could just fill holeRect, but it's more efficient to only fill the
// area we're being asked to draw.
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
[[UIColor clearColor] setFill];
UIRectFill( holeRectIntersection );
}
If you're using Interface builder, make sure to change the holey view's class to HoleyView. You can do that by selecting in the view in Interface Builder and selecting the "Identity" pane in the inspector (its the one on the far right the the "i" icon).
You also have to set the top view to be non-opaque either with the following code snippet, or by un-checking the Opaque checkbox in the view's properties in Interface Builder (you'll find it in the View section of the view's attributes) and set its background color's opacity to 0% (background color is set in the same section).
topView.opaque = NO;
topView.backgroundColor = [UIColor clearColor];
If you want to do circles, you have to use Core Graphics (aka Quartz 2D). You'll probably want to read the programming guide, which is available here.
To draw an ellipse instead of the rectangle, your drawRect would look something like this:
- (void)drawRect:(CGRect)rect {
// Get the current graphics context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor( context, [UIColor blueColor].CGColor );
CGContextFillRect( context, rect );
if( CGRectIntersectsRect( holeRect, rect ) )
{
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextFillEllipseInRect( context, holeRect );
}
}
There is truth in all the other answers, but it is quite possible to draw with clear colour, or so to say erase the existing colours within any path, even with the -[UIBezierPath fill] or similar convenience methods. All you have to do is to set the context blend mode to an appropriate value depending on the effect you are trying to achieve, like so:
CGContextSetBlendMode(context, kCGBlendModeClear);
[[UIColor clearColor] set];
[myArbitraryPolygonPath fill];
There are around two dozen different options you could choose from, take a look around the CGContext reference.
to draw an ellipse, instead of a rect, just set the blendMode to clear:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor( context, [UIColor blackColor].CGColor );
CGContextFillRect( context, rect );
CGRect holeRectIntersection = CGRectIntersection( holeRect, rect );
CGContextSetFillColorWithColor( context, [UIColor clearColor].CGColor );
CGContextSetBlendMode(context, kCGBlendModeClear);
CGContextFillEllipseInRect( context, holeRect );
}
These may be the dumb ways but I would not do it the way you describe, but make it look the way you want it to look.
(Jacques' answer just popped up - looks good to me)
Approach 1:
In the view controller to build a list of rectangles that tile around the exposed "hole". As your holes increase in number the number of tiling rects will also increase.
Approach 2:
Reverse your thinking. The blue view should be at the back with sections of the red view placed on top of it. You still see a red view with all but the "holes" masked by the blue view, but what you are really doing is copying regions from the view you want to expose and putting them on top of the mask as you make each hole. If you have some effect simulating depth you could add that as required with each hole.
Neither requires subclassing or drawRect:.
Some of the answers are quite old.
2023, Latest techniques include...
class CompImage: UIIImageView {
override func common() {
super.common()
layer.compositingFilter = "multiplyBlendMode"
print("Filters available to you ...\n",
CIFilter.filterNames(inCategory: kCICategoryCompositeOperation))
}
}
and
guard let ctx = UIGraphicsGetCurrentContext() else { return }
ctx.saveGState()
ctx.setStrokeColor(
..
ctx.setBlendMode(.multiply) // review the choices available
..

Resources