iPhone: Create CGLayer with gradient point and transparent background - iphone

I would like to create a CGLayer, contains a point with gradient in boundary. Because I will use this layer to draw multiple points on the main screen.
This is my code:
CGRect r = CGRectMake(0, 0, 64, 64);
textureLayer = CGLayerCreateWithContext(context, r.size, NULL);
CGContextRef textureContext = CGLayerGetContext(textureLayer);
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGFloat colors[] =
{
0.0, 0.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0
};
gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4));
CGColorSpaceRelease(rgb);
CGPoint start, end;
start = end = CGPointMake(32, 32);
CGFloat startRadius = 20;
CGFloat endRadius = 30;
CGContextSaveGState(textureContext);
CGGradientDrawingOptions options = 0 | kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
CGContextDrawRadialGradient(textureContext, gradient, start, startRadius, end, endRadius, options);
CGContextRestoreGState(textureContext);
I got this result:
It is correct. But when I draw on main screen, not the stroke like I expect, this is the result:
I think the problem is the white background in my CGLayer. How I can make that background be transparent?
Thank for your help.

Related

drawRect Circle inside of circle used to show progress

I'm trying to show progress by having a circle fill up with another circle inside of it. Can someone help me achieve this? I may be going about this the wrong way.
Here is my code:
- (void)drawRect:(CGRect)rect
{
rect = CGRectMake(0, 400, 500, 500);
[[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 500, 500) cornerRadius:50.0] addClip];
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(contextRef, 2.0);
CGContextSetRGBFillColor(contextRef, 111/255.0f, 116/255.0f, 128/255.0f, 1.0);
CGRect circlePoint = (CGRectMake(0, 0, rect.size.width, rect.size.height));
CGContextFillEllipseInRect(contextRef, circlePoint);
CGContextBeginPath(contextRef);
float c = 20; //Number that should cause the green progress to increase and decrease
CGContextAddArc(contextRef, 250, 250, 250, 0, M_PI, 0);
CGContextClosePath(contextRef); // could be omitted
UIColor *color = [UIColor greenColor];
CGContextSetFillColorWithColor(contextRef, color.CGColor);
CGContextFillPath(contextRef);
}
And here is a screenshot of how it looks right now. I basically want to be able to control the progress of the green inside using my float c; variable.
I've tried playing around with CGContextAddArc, but I can't get grow to the size of the circle as "c" increases and same when I decrease the value of "c"
use your c to say from where to where
// draw the pie part
CGContextMoveToPoint(_viewContext, centerX, centerY);
CGContextAddArc(_viewContext, centerX, centerY, pieRadius, radians(startDegrees), radians(endDegrees), 0);
CGContextClosePath(_viewContext);
CGContextFillPath(_viewContext);
startDegrees = 0
endDegress = c
_viewContext = contextRef
centerX/CenterY = center of bounds
PieRadius = radius you want :D in pixels IIRC
to convert deg to rad
#define pi 3.14159265
double radians(double percentage)
{
return (percentage/100)*360 * (pi/180);
}

How to draw a full circle with image

I'm pretty new with core graphics. I need to draw a full circle. How do I go about it? I currently have a masked circle.
I referred to this example.
//Use the draw rect to draw the Background, the Circle and the Handle
-(void)drawRect:(CGRect)rect{
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
/** Draw the Background **/
//Create the path
CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, radius, 0, M_PI *2, 0);
// CGContextFillEllipseInRect(ctx, CGRectMake(self.frame.size.height/2, self.frame.size.width/2, 100, 100));
// CGContextSetRGBFillColor(ctx, 0.5, 0.5, 0.5, 1.0);
//Set the stroke color to black
[[UIColor greenColor]setStroke];
//Define line width and cap
CGContextSetLineWidth(ctx, TB_BACKGROUND_WIDTH);
CGContextSetLineCap(ctx, kCGLineCapButt);
CGContextDrawPath(ctx, kCGPathStroke);
UIGraphicsBeginImageContext(CGSizeMake(TB_SLIDER_SIZE,TB_SLIDER_SIZE));
CGContextRef imageCtx = UIGraphicsGetCurrentContext();
CGContextAddArc(imageCtx, self.frame.size.width/2 , self.frame.size.height/2, radius, 0, ToRad(self.self.startAngle), 0);
[[UIColor redColor]set];
CGContextSetLineWidth(imageCtx, TB_LINE_WIDTH);
CGContextDrawPath(imageCtx, kCGPathStroke);
//save the context content into the image mask
CGImageRef mask = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
CGContextSaveGState(ctx);
CGContextClipToMask(ctx, self.bounds, mask);
CGImageRelease(mask);
//list of components
CGFloat components[8] = {
0.0, 0.0, 1.0, 1.0, // Start color - Blue
1.0, 1.0, 1.0, 1.0 }; // End color - Violet
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, components, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
//Gradient direction
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
//Draw the gradient
CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(ctx);
/** Draw the handle **/
[self drawTheHandle:ctx];
}
CGContextDrawPath(imageCtx, kCGPathFill);
make the backgroup width and line width to 140 and it works!!
#define TB_BACKGROUND_WIDTH 140 //The width of the dark background
#define TB_LINE_WIDTH 140 //The width of the active area (the gradient) and the width of the handle

Chrome Text Effect With Linear Gradient Gives Jagged Edges

I am trying to generate a chrome text effect in core graphics and I am having some trouble getting it to look right.
This is the effect I am trying to achieve:
This is what I have managed to get:
As you can see the gradient edge is quite jagged. Is there a way to achieve this effect without resorting to static pngs for the texture fill?
This is the my code:
- (void)setGradientFill:(UILabel*)label
{
CGSize textSize = [label.text sizeWithFont:label.font];
CGFloat width = textSize.width; // max 1024 due to Core Graphics limitations
CGFloat height = textSize.height; // max 1024 due to Core Graphics limitations
// create a new bitmap image context
UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), YES, 0.0);
// get context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAllowsAntialiasing(context, true);
CGContextSetShouldAntialias(context, true);
// push context to make it current (need to do this manually because we are not drawing in a UIView)
UIGraphicsPushContext(context);
CGContextSetAllowsAntialiasing(context, YES);
CGContextSetShouldAntialias(context, YES);
//draw gradient
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 5;
CGFloat locations[5] = { 0.0, 0.5, 0.5, 0.65, 1};
CGFloat components[20] = {
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
0.878, 0.878, 0.878, 0.878,
0.78, 0.78, 0.78, 1.0,
1, 1, 1, 1.0
};
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGPoint start = CGPointMake(0, 0);
CGPoint end = CGPointMake(width/4, height*1.2);
CGContextDrawLinearGradient(context, glossGradient, start, end, kCGGradientDrawsAfterEndLocation);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
// pop context
UIGraphicsPopContext();
// get a UIImage from the image context
UIImage *gradientImage = UIGraphicsGetImageFromCurrentImageContext();
// clean up drawing environment
UIGraphicsEndImageContext();
label.textColor = [UIColor colorWithPatternImage:gradientImage];
}
Edit for suggestion by lefteris:
I tried the extension you made (image on the left below). I am getting some very blurry text with artefacts around the edges for some reason.
I am sizing the UIImage frame to the image to avoid content stretching:
_test.frame = CGRectMake(_test.frame.origin.x, _test.frame.origin.y, image.size.width, image.size.height);
_test.image = image;
Edit Closest Solution:
Solution by lefteris is the closest, you can't really avoid the jagged gradient edges though. If you can you should use static pngs generated in photoshop for the texture (not the text just the fill) but you may need several versions (as I do) to get the right effect on each screen as the texture is applied according to the size of the frame not the contained text. Doing it dynamically is possible but limited it seems.
Use the UIGraphicsBeginImageContextWithOptions function with the scale factor set to 0.0.
The UIGraphicsBeginImageContext function calls that function with the scale factor set to 1.0, unconditionally, even on Retina (#2x) devices. On a Retina device, you want 2.0. 0.0 will automatically detect the correct scale factor to use and use it.
You should also smooth out your gradient.
Use this instead:
CGFloat locations[5] = { 0.0, 0.45, 0.55, 0.65, 1};
I edit my answer to provide a different solution/suggestion:
I have created a 3D Text rendering UIImage Extension that renders 3D Text, which can be found here
Using that category extension, with a bit of modifications, I ended up with this image:
I believe you should not create the gradient as a background color and apply it as a whole, over a label, as it is also adding the gradient over the background as well, which is making it look less realistic.
I created the image above, with these settings:
UIImage *my3dImage = [UIImage create3DImageWithText:#"61" Font:[UIFont fontWithName:#"Helvetica-Bold" size:180] ForegroundColor:[UIColor colorWithRed:(255/255.f) green:(255/255.f) blue:(255/255.f) alpha:1.0] ShadowColor:[UIColor blackColor] outlineColor:[UIColor lightGrayColor] depth:4 useShine:YES];
And I modified my source code to apply a different shine effect:
size_t num_locations = 5;
CGFloat locations[5] = { 0.0, 0.49, 0.51, 0.65, 1};
CGFloat gradientComponents[20] = {
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0,
0.878, 0.878, 0.878, 0.878,
0.78, 0.78, 0.78, 1.0,
1, 1, 1, 1.0
};
CGGradientRef glossGradient = CGGradientCreateWithColorComponents(genericRGBColorspace, gradientComponents, locations, num_locations);
CGPoint start = CGPointMake(0, expectedSize.height/4);
CGPoint end = CGPointMake(expectedSize.width/4, expectedSize.height);
The reason, I believe my approach is better, is because it actually is creating a mask from the text and then applies the gradient, meaning only the text get's the gradient and not the background.

Advanced gradients?

Is it possibly to have a gradient where there's a colour in each corner of a UIView?
There's no built-in way to do this, and no simple way to do this via Quartz either. However, you can do it via Quartz by creating 4 different 2-D gradients going in different directions, and masking each one with a black-white gradient running perpendicular.
Assuming your colors are defined as: tlColor, trColor, blColor, brColor (for top-left, top-right, etc.).
Vertical gradient from tlColor (top) to blColor (bottom).
Horizontal gradient from white (left) to black (right), convert to image, use as image mask with image from #1.
Vertical gradient from trColor (top) to brColor (bottom).
Horizontal gradient from black (left) to white (right), convert to image, use as image mask with image from #3.
Horizontal gradient from blColor (left) to trColor (right).
Vertical gradient from white (top) to black (bottom), convert to image, use as image mask with image from #5.
Horizontal gradient from tlColor (left) to brColor (right).
Vertical gradient from black (top) to white (bottom), convert to image, use as image mask with image from #7.
Then just draw each of the 4 results to the view.
You might want to try this in Acorn or Photoshop first... so it's clear what's involved before you translate it into code. Then the Quartz reference #lxt pointed you to has sections on gradients (CGGradientRef is sufficient, don't try to use shadings), and a separate section on image masks.
I have 2 gradients placed diagonal. With overlay-blending
- (void)drawRect:(CGRect)rect
{
CGContextRef currentContext = UIGraphicsGetCurrentContext();
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat locations2[2] = { 1.0, 0.0 };
CGFloat components[8] = {
1.0, 1.0, 0.0, 1.0 ,//Yellow
0.0, 0.0, 1.0, 1.0};//Blue
CGColorSpaceRef rgbColorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGPoint point1 = CGPointMake(0, 0);
CGPoint point2 = CGPointMake(self.bounds.size.width, self.bounds.size.height);
CGContextDrawLinearGradient(currentContext, gradient, point1, point2, 0);
CGFloat components1[8] = {
0.0, 1.0, 0.0, 1.0,//Red
1.0, 0.0, 0.0, 1.0}; //Green
gradient = CGGradientCreateWithColorComponents(rgbColorspace, components1, locations2, num_locations);
CGContextSetBlendMode(currentContext, kCGBlendModeOverlay);
point1 = CGPointMake(self.bounds.size.width, 0);
point2 = CGPointMake(0, self.bounds.size.height);
CGContextDrawLinearGradient(currentContext, gradient, point1, point2, 0);
CGGradientRelease(gradient);
CGColorSpaceRelease(rgbColorspace);
}
To change the color you should have 4 UIColors as members on the view and change this fragment accordingly.
Here is a working solution.
One vertical gradient bitmap mask used both as is and mirrored when drawing two color gradients.
FourTimesRGBAcomponents is the R,G,B,A components of the four corner colors UL, UR, LL and LR, i.e. ULR, ULG, ULB, ULA, URR etc.
void DrawFourGradientRect(CGContextRef Cgc,
const CGRect * Rect,
const CGFloat FourTimesRGBAcomponents[16])
{
int w;
int h;
void *MaskData;
CGColorSpaceRef GrayCS;
CGColorSpaceRef CS;
CGContextRef BC; //Bitmap context
CGFloat GrayComp[4] = {1.0, 1.0, 0.0, 1.0}; //2 * Gray+Alpha
CGGradientRef Gradient;
CGImageRef Mask;
w = Rect->size.width;
h = Rect->size.height;
//Start filling with white
CGContextSetRGBFillColor(Cgc, 1, 1, 1, 1);
CGContextFillRect(Cgc, *Rect);
//Then create vertical gradient mask bitmap, white on top, black at bottom
MaskData = malloc(w * h);
if (!MaskData)
return;
GrayCS = CGColorSpaceCreateDeviceGray();
if (!GrayCS)
{
free(MaskData);
return;
}
BC = CGBitmapContextCreate(MaskData, w, h, 8, w, GrayCS, kCGImageAlphaNone);
if (!BC)
{
CGColorSpaceRelease(GrayCS);
free(MaskData);
return;
}
Gradient = CGGradientCreateWithColorComponents(GrayCS, GrayComp, NULL, 2);
if (!Gradient)
{
CGContextRelease(BC);
CGColorSpaceRelease(GrayCS);
free(MaskData);
return;
}
CGColorSpaceRelease(GrayCS);
CGContextDrawLinearGradient(BC, Gradient, CGPointZero, CGPointMake(0, h), 0);
CGGradientRelease(Gradient);
Mask = CGBitmapContextCreateImage(BC);
CGContextRelease(BC);
free(MaskData);
if (!Mask)
return;
//Now draw first horizontal color gradient from UL to UR
CS = CGColorSpaceCreateDeviceRGB();
if (!CS)
{
CGImageRelease(Mask);
return;
}
CGContextSaveGState(Cgc);
CGContextTranslateCTM(Cgc, Rect->origin.x, Rect->origin.y);
CGContextClipToMask(Cgc, CGRectMake(0, 0, w, h), Mask);
Gradient = CGGradientCreateWithColorComponents(CS,
FourTimesRGBAcomponents, NULL, 2);
if (Gradient)
{
CGContextDrawLinearGradient(Cgc, Gradient, CGPointZero, CGPointMake(w, 0), 0);
CGGradientRelease(Gradient);
}
CGContextRestoreGState(Cgc);
//Finally draw second horizontal color gradient from LL to LR
CGContextSaveGState(Cgc);
CGContextTranslateCTM(Cgc, Rect->origin.x, Rect->origin.y + Rect->size.height);
CGContextScaleCTM(Cgc, 1, -1); //Use vertical gradient mask upside-down
CGContextClipToMask(Cgc, CGRectMake(0, 0, w, h), Mask);
Gradient = CGGradientCreateWithColorComponents(CS,
FourTimesRGBAcomponents + 8, NULL, 2);
if (Gradient)
{
CGContextDrawLinearGradient(Cgc, Gradient, CGPointZero, CGPointMake(w, 0), 0);
CGGradientRelease(Gradient);
}
CGContextRestoreGState(Cgc);
CGImageRelease(Mask);
CGColorSpaceRelease(CS);
} /* DrawFourGradientRect */
To be a little more helpful than Carl, the answer is 'yes', but how you implement it would depend on the tech you wanted to use. The Quartz gradient system is described here:
http://developer.apple.com/library/mac/#documentation/graphicsimaging/Conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html
...but OpenGL is another possibility. It would depend on your application.

UIImage Shadow Trouble

I'm trying to add a small shadow to an image, much like the icon shadows in the App Store. Right now I'm using the following code to round the corners of my images. Does anyone know how I can adapt it to add a small shadow?
- (UIImage *)roundCornersOfImage:(UIImage *)source height:(int)height width:(int)width {
int w = width;
int h = height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef imageContext = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextBeginPath(imageContext);
CGRect rect = CGRectMake(0, 0, w, h);
addRoundedRectToPath(imageContext, rect, 10, 10);
CGContextClosePath(imageContext);
CGContextClip(imageContext);
CGContextDrawImage(imageContext, CGRectMake(0, 0, w, h), source.CGImage);
CGImageRef imageMasked = CGBitmapContextCreateImage(imageContext);
CGContextRelease(imageContext);
CGColorSpaceRelease(colorSpace);
return [UIImage imageWithCGImage:imageMasked];
}
addRoundedRectToPath refers to another method that obviously rounds the corners.
First, here's a link to the documentation:
http://developer.apple.com/iPhone/library/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_shadows/dq_shadows.html#//apple_ref/doc/uid/TP30001066-CH208-TPXREF101
Next, try adding something like this right before the call to CGContextDrawImage(...):
CGFloat components[4] = {0.0, 0.0, 0.0, 1.0};
CGColorRef shadowColor = CGColorCreate(colorSpace, components);
CGContextSetShadowWithColor(imageContext, CGSizeMake(3, 3), 2, shadowColor);
CGColorRelease(shadowColor);
After, the call to CGContextSetShadowWithColor(.....), everything should draw with a shadow that is offset by (3, 3) points, and drawn with a 2.0 point blur radius. You'll probably want to tweak the opacity of the black color (the forth component in components), and change the shadow parameters.
If you'd like to stop drawing with a shadow at some point, you need to save the graphics context before calling CGContextSetShadowWithColor, and restore it when you want to stop drawing with a shadow.