In my UITableViewCell there is a background UIImageView. If I use the raw image download from server there is no blended layer showed on that ImageView. But if I add gradient to the image downloaded (some images contain lots of white space which make it hard to see the front label, so I have to make the image darker), blended layer showed. How can I add gradient to the image while avoiding blended layer?
The code used to generate image with gradient is showed below:
- (UIImage *)gs_imageWithDownsideShadow {
return [self gs_imageWithShadow:#[[UIColor gs_colorWithSameRGB:0 alpha:0.3], [UIColor gs_colorWithSameRGB:0 alpha:0.5]]];
}
- (UIImage *)gs_imageWithShadow:(NSArray *)colors
{
CGSize size = self.size;
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, UIScreen.mainScreen.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, rect, self.CGImage);
CGFloat locations[] = {0.0, 1.0};
NSMutableArray *colorRefs = [[NSMutableArray alloc] init];
for (UIColor *color in colors) {
[colorRefs addObject:(__bridge id)color.CGColor];
}
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(baseSpace, (__bridge CFArrayRef)colorRefs, locations);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(baseSpace);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
A layer whose contents is an image with an alpha channel must always be blended with the layers behind it. (iOS assumes that the image has transparency if it has an alpha channel; it doesn't check whether any of the alphas are less than 1.0.)
You're creating gradient-ized images with alpha channels because you're passing NO to the opaque argument of UIGraphicsBeginImageContextWithOptions.
Assuming all of your source images are in fact opaque, you can (and should) create opaque gradient-ized images by passing YES:
UIGraphicsBeginImageContextWithOptions(size, YES, UIScreen.mainScreen.scale);
Passing YES tells UIKit to create a graphics context, and eventually an image, with no alpha channel.
Related
I want to crop an image with feathered circle. I use this to crop an image but it is only cropped square.
CGImageRef imref = CGImageCreateWithImageInRect([newImage CGImage], faceRect);
newSubImage = [UIImage imageWithCGImage:imref];
What I want is to crop with feather edges? What should I use to achieve it?
This snippet will create a circular cut with feathered edges
The first featherLocations variable is likely the only one you'll need to adjust
- (UIImage *) featheredImageWithImage:(UIImage *) image
{
// Locations of where the feather starts and ends (0 -> 1)
const CGFloat featherLocations[] = {0.9, 1};
UIGraphicsBeginImageContextWithOptions(image.size, NO, image.scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
// Draw the original image
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height)];
// A 'knock-out' gradient is used to generate a feather effect,
// the alpha channel on the colors defines the alpha of the drawn image
NSArray *gradientColors = #[(id)[UIColor colorWithWhite:0 alpha:1].CGColor,
(id)[UIColor colorWithWhite:0 alpha:0].CGColor];
CGGradientRef gradient = CGGradientCreateWithColors(CGImageGetColorSpace(image.CGImage), (__bridge CFArrayRef)gradientColors, featherLocations);
// Because we're changing the draw mode below,
// take a snapshot of the current draw settings so we can reset them after
CGContextSaveGState(ctx);
// The kCGBlendModeDestinationIn blend mode will provide a'knock-out' effect on
// the previously drawn content, using the alpha channels of the gradient's colors
CGContextSetBlendMode(ctx, kCGBlendModeDestinationIn);
const CGPoint gradientCenter = CGPointMake(image.size.width / 2, image.size.height / 2);
// The gradient will start at the center (0) and extend to the closest edge (horizontal or vertical)
const CGFloat startRadius = 0;
const CGFloat endRadius = MIN(image.size.width,
image.size.height) / 2;
// Draw the gradient to eliminate the pixels we don't want
CGContextDrawRadialGradient(ctx, gradient, gradientCenter, startRadius, gradientCenter, endRadius, (kCGGradientDrawsAfterEndLocation));
CGGradientRelease(gradient);
gradient = NULL;
// Finally, restore state
// (note that in this example CGContextSaveGState and CGContextRestoreGState
// are optional because no further drawing happens after this point)
CGContextRestoreGState(ctx);
// Get the UIImage version
UIImage *featheredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return featheredImage;
}
What about this... first round the image:
//round the image
UIImageView *roundView = [[UIImageView alloc] initWithImage:smallImage];
UIGraphicsBeginImageContextWithOptions(roundView.bounds.size, NO, [UIScreen mainScreen].scale);
[[UIBezierPath bezierPathWithRoundedRect:roundView.bounds
cornerRadius:roundView.frame.size.width/2] addClip];
[smallImage drawInRect:roundView.bounds];
UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
and then round it again but with a smaller circle and set the bezier path colour again to white but make it semi transparent by adjusting the alpha...
How can I fill the non-transparent areas of a PNG UIImage with a linear gradient? I'd like to reuse a PNG shape for MKAnnotationViews, but change the gradient per annotation's properties.
To use an image as a mask for a gradient (i.e. to have a gradient in the shape of the non-transparent pixels of your image), you can:
create a simple view with a gradient (you can either create a simple UIView and use the addGradientLayerToView shown below to give it a gradient or you can create the gradient PNG in advance and add it to your bundle).
apply your PNG as a mask to that gradient view:
UIImage *mask = [UIImage imageNamed:#"mask.png"];
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = CGRectMake(0, 0, mask.size.width, mask.size.height);
maskLayer.contents = (id)[mask CGImage];
gradientViewToMask.layer.mask = maskLayer;
To apply a gradient to the transparent pixels, you can either:
Create a new image with a gradient:
- (UIImage *)imageWithGradient:(UIImage *)image
{
UIGraphicsBeginImageContextWithOptions(image.size, NO, 1.0);
CGContextRef context = UIGraphicsGetCurrentContext();
size_t locationCount = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 0.0, 0.8, 0.8, 1.0, // Start color
0.9, 0.9, 0.9, 1.0 }; // End color
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents (colorspace, components, locations, locationCount):
CGPoint startPoint = CGPointMake(0.0, 0.0);
CGPoint endPoint = CGPointMake(0.0, image.size.height);
CGContextDrawLinearGradient (context, gradient, startPoint, endPoint, 0);
CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, CGRectMake(0.0, 0.0, image.size.width, image.size.height), [image CGImage]);
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGGradientRelease(gradient);
CGColorSpaceRelease(colorspace);
return gradientImage;
}
You can also add a CAGradientLayer to a view and then add the UIImageView as a subview of that view.
- (void)addGradientLayerToView:(UIView *)view
{
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = #[(id)[[UIColor colorWithRed:0.0 green:0.8 blue:0.8 alpha:1.0] CGColor],
(id)[[UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:1.0] CGColor]];
[view.layer insertSublayer:gradient atIndex:0];
}
Note, you have to #import <QuartzCore/QuartzCore.h> as well as add the QuartzCore framework to your project.
I ended up hacking together some bits of Rob's code and an extension to UIImage I found at http://coffeeshopped.com/2010/09/iphone-how-to-dynamically-color-a-uiimage
+ (UIImage *)imageNamed:(NSString *)name withGradient:(CGGradientRef)gradient
{
// load the image
UIImage *img = [UIImage imageNamed:name];
// begin a new image context, to draw our colored image onto
UIGraphicsBeginImageContextWithOptions(img.size, NO, [[UIScreen mainScreen] scale]);
// get a reference to that context we created
CGContextRef context = UIGraphicsGetCurrentContext();
// 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 overlay, and the original image
CGContextSetBlendMode(context, kCGBlendModeOverlay);
CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
// set a mask that matches the shape of the image, then draw (overlay) a colored rectangle
CGContextClipToMask(context, rect, img.CGImage);
CGContextAddRect(context, rect);
//gradient
CGPoint startPoint = CGPointMake(0.0, img.size.height);
CGPoint endPoint = CGPointMake(0.0, 0.0);
CGContextDrawLinearGradient (context, gradient, startPoint, endPoint, 0);
// generate a new UIImage from the graphics context we drew onto
UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGGradientRelease(gradient);
//return the color-burned image
return coloredImg;
}
I'm trying to overlay a color on a UIImage, but only on the left half of the image (I'm using code from http://coffeeshopped.com/2010/09/iphone-how-to-dynamically-color-a-uiimage to overlay the color). The code I have now is:
- (UIImage *)imageWithColor:(UIColor *)color{
// begin a new image context, to draw our colored image onto
CGSize size = CGSizeMake(self.line.image.size.width/2, self.line.image.size.height);
UIGraphicsBeginImageContextWithOptions(size, NO, [[UIScreen mainScreen] scale]);
// get a reference to that context we created
CGContextRef context = UIGraphicsGetCurrentContext();
// set the fill color
[color setFill];
// translate/flip the graphics context (for transforming from CG* coords to UI* coords
CGContextTranslateCTM(context, 0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// set the blend mode to overlay, and the original image
CGContextSetBlendMode(context, kCGBlendModeOverlay);
CGRect rect = CGRectMake(0, 0, size.width, size.height);
CGContextDrawImage(context, rect, self.line.image.CGImage);
// set a mask that matches the shape of the image, then draw (overlay) a colored rectangle
CGContextClipToMask(context, rect, self.line.image.CGImage);
CGContextAddRect(context, rect);
CGContextDrawPath(context,kCGPathFill);
// generate a new UIImage from the graphics context we drew onto
UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//return the color-burned image
return coloredImg;
}
I thought setting the size to be half the width would work, but everything still gets color. I guess I'm missing something very fundamental. Any ideas?
In
CGContextAddRect(context, rect);
you are adding a rectangle with the full size.
I know of several ways of adding background gradients to UIView. I was wondering what is the most efficient and scalable way of doing so, and why? Here are the techniques I've used:
Create subview of UIView and overwrite the drawRect, where I draw the gradient in current context.
a. When using the above gradient create it with the bounds of view I want to decorate and insert this background gradient as first subview, i.e. – insertSubview:atIndex:
b. After I get the background gradient view from above, I render it in image context and use it as background image, i.e.
UIGraphicsBeginImageContext(gradView.bounds.size);
[gradView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *gradientImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIColor *background = [UIColor colorWithPatternImage:gradientImg];
self.view.backgroundColor = background;
Create stretchable PNG and use that to decorate view background. Technique is very similar to the way you decorate UIButton.
-(UIColor *)backgroundColor:(CGRect)frame
{
UIImage *bg = [UIImage imageNamed:#"background_23x36"];
bg = [bg resizableImageWithCapInsets:UIEdgeInsetsMake(0.0, 11.0, 0.0, 11.0)];
UIGraphicsBeginImageContextWithOptions(frame.size, YES, [[UIScreen mainScreen] scale]);
[bg drawInRect:frame];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [UIColor colorWithPatternImage:image];
}
Create subview of UIView but instead of overwriting the drawRect, use CAGradientLayer. Similar to http://nscookbook.com/2013/04/recipe-20-using-cagradient-layer-in-a-custom-view/
So what is most efficient and scalable way to add gradient as a background?
As Fogmeister suggested above, do it in the drawRect: method of your UIView subclass.
- (void)drawRect:(CGRect)rect
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = UIGraphicsGetCurrentContext();
NSArray *gradientColors = [NSArray arrayWithObjects:(id) [UIColor redColor].CGColor, [UIColor yellowColor].CGColor, nil];
CGFloat gradientLocations[] = {0, 0.50, 1};
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) gradientColors, gradientLocations);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
DrawRect is the more efficient way i think. I'd like to extend jverrijt's nice answer. If you need alpha components (transparent to black gradient f.e.), then you can use this:
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
CGContextRestoreGState(UIGraphicsGetCurrentContext());
//Draw stuffs
UIGraphicsEndImageContext();
How can you mask a square image into an image with round corners?
You can use CoreGraphics to create a path for a round rectangle with this code snippet:
static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight)
{
float fw, fh;
if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM (context, ovalWidth, ovalHeight);
fw = CGRectGetWidth (rect) / ovalWidth;
fh = CGRectGetHeight (rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
CGContextClosePath(context);
CGContextRestoreGState(context);
}
And then call CGContextClip(context); to clip it to the rectangle path. Now any drawing done, including drawing an image, will be clipped to the round rectangle shape.
As an example, assuming "image" is a UIImage, and this is in a drawRect: method:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
addRoundedRectToPath(context, self.frame, 10, 10);
CGContextClip(context);
[image drawInRect:self.frame];
CGContextRestoreGState(context);
Here is an even easier method that is available in iPhone 3.0 and up. Every View-based object has an associated layer. Each layer can have a corner radius set, this will give you just what you want:
UIImageView * roundedView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:#"wood.jpg"]];
// Get the Layer of any view
CALayer * layer = [roundedView layer];
[layer setMasksToBounds:YES];
[layer setCornerRadius:10.0];
// You can even add a border
[layer setBorderWidth:4.0];
[layer setBorderColor:[[UIColor blueColor] CGColor]];
To use these methods you might need to add:
#import <QuartzCore/QuartzCore.h>
I realize this is old news but just to boil it down a bit:
There are two possible questions here: (1) how do I apply rounded corners to a UIView (such as a UIImageView), which will be displayed on screen, and (2) how do I mask a square image (that is, a UIImage) to produce a new image with rounded corners.
For (1), the easiest course is to use CoreAnimation and set the view.layer.cornerRadius property
// Because we're using CoreAnimation, we must include QuartzCore.h
// and link QuartzCore.framework in a build phases
#import <QuartzCore/QuartzCore.h>
// start with an image
UIImage * fooImage = [UIImage imageNamed:#"foo.png"];
// put it in a UIImageView
UIView * view = [UIImageView alloc] initWithImage:fooImage];
// round its corners. This mask now applies to the view's layer's *background*
view.layer.cornerRadius = 10.f
// enable masksToBounds, so the mask applies to its foreground, the image
view.layer.masksToBounds = YES;
For (2), the best way is to use the UIKit graphics operations:
// start with an image
UIImage * fooImage = [UIImage imageNamed:#"foo.png"];
CGRect imageRect = CGRectMake(0, 0, fooImage.size.width, fooImage.size.height);
// set the implicit graphics context ("canvas") to a bitmap context for images
UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0);
// create a bezier path defining rounded corners
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:10.f];
// use this path for clipping in the implicit context
[path addClip];
// draw the image into the implicit context
[fooImage drawInRect:imageRect];
// save the clipped image from the implicit context into an image
UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();
// cleanup
UIGraphicsEndImageContext();
What's tricky about problem (2) is that you might think you could do the whole operation using the view.layer.mask property in CoreAnimation. But you can't because the CALayer renderInContext: method, which you'd use to generate a UIImage from the masked layer, seems to ignore the mask. Worse, the documentation for renderInContext: doesn't mention this, and only alludes to the behavior for OSX 10.5.
Some further context: the above approach to (2) is using UIKit's wrappers around more basic CoreGraphics functionality. You can do the same thing using the CoreGraphics calls directly – that is what the chosen answer is doing -- but then you need build the rounded rect bezier path manually from curves and lines and you also need to compensate for the fact that CoreGraphics uses a drawing coordinate system which is flipped with respect to UIKit's.
See this Post - Very simple answer
How to set round corners in UI images in iphone
UIImageView * roundedView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:#"wood.jpg"]];
// Get the Layer of any view
CALayer * l = [roundedView layer];
[l setMasksToBounds:YES];
[l setCornerRadius:10.0];
Very simple.
self.profileImageView.layer.cornerRadius = self.profileImageView.frame.size.width / 2;
self.profileImageView.clipsToBounds = YES;
For every view, there is a bundled layer property. So the first line of the above is to set the corner radius of the layer object (i.e. an instance of CALayer class). To make a circular image from a squared image, the radius is set to the half of the width of UIImageView. For instance, if the width of squared image is 100 pixels. The radius is set to 50 pixels. Secondly, you have to set the clipsToBounds property to YES in order to make the layer works.
Both the methods work but the differences shows up depending on where you use it.
For Ex: If you have a table view with the cells showing an image along with other labels, etc., and you use layer to set the cornerRadius, the scrolling will take a big hit. It gets jerky.
I faced this issue when I was using Layer for an image in a table view cell and was trying to figure out the cause of that jerkiness only to find that CALayer was the culprit.
Used the first solution of doing the stuff in drawRect explained by NilObject. That works like a charm with scrolling being smooth as silk.
On the other hand, if you want to use this in static views like popover view, etc., layer is the easiest way to do it.
As I said, both the methods work well just that you need to decide based on where you want to use it.
I use this method.
+ (UIImage *)imageWithColor:(UIColor *)color andSize:(CGSize)size;
{
UIImage *img = nil;
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context,
color.CGColor);
CGContextFillRect(context, rect);
img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
Building off of algal, here are a couple methods that are nice to put in an UIImage category:
- (UIImage *) roundedCornerImageWithRadius:(CGFloat)radius
{
CGRect imageRect = CGRectMake(0, 0, self.size.width, self.size.height);
UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0); //scale 0 yields better results
//create a bezier path defining rounded corners and use it for clippping
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:radius];
[path addClip];
// draw the image into the implicit context
[self drawInRect:imageRect];
// get image and cleanup
UIImage *roundedCornerImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return roundedCornerImage;
}
+ (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)size andCornerRadius:(CGFloat)radius
{
UIImage *image = nil;
if (size.width == 0 || size.height == 0) {
size = CGSizeMake(1.0, 1.0);
}
CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
UIGraphicsBeginImageContextWithOptions(rect.size,NO,0.0); //yields sharper results than UIGraphicsBeginImageContext(rect.size)
CGContextRef context = UIGraphicsGetCurrentContext();
if (context)
{
CGContextSetFillColorWithColor(context, [color CGColor]);
if (radius > 0.0) {
//create a bezier path defining rounded corners and use it for clippping
UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
[path addClip];
CGContextAddPath(context, path.CGPath);
}
CGContextFillRect(context, rect);
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
return image;
}