Chrome Text Effect With Linear Gradient Gives Jagged Edges - iphone

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.

Related

Stroke is being applied to both Text and Shadow

I'm trying to simply draw some text with a stroke and then apply a drop shaddow, but the stroke is being applied to drop shadow. How do I prevent this?
// Setup context
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
// draw photo into context
CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
// Set Text Font
CGContextSelectFont(context, font.fontName.UTF8String, fontSize, kCGEncodingMacRoman);
// Set Text Drawing Mode
CGContextSetTextDrawingMode(context, kCGTextFillStroke);
// Set text fill color
CGColorRef tmpColor = color.CGColor;
CGFloat newComponents[4] = {};
memcpy(newComponents, CGColorGetComponents(tmpColor), sizeof(newComponents));
CGContextSetRGBFillColor(context, newComponents[0], newComponents[1], newComponents[2], newComponents[3]);
// Calculate Height
UIFont *testFont = [UIFont fontWithName:font.fontName size:fontSize];
CGSize size = [text sizeWithFont:testFont];
// Setup Font Shadow
CGSize shadowSize = CGSizeMake(6, 6);
CGContextSetShadowWithColor(context, shadowSize, 1.0, [[UIColor darkGrayColor] CGColor]);
// Set Stroke Color
CGContextSetRGBStrokeColor(context, 0, 255, 0, 1);
CGContextSetLineWidth(context, 2.0);
// draw text at location centered based on width.
CGContextShowTextAtPoint(context, (w / 2) - (textWidth / 2), h - size.height, ctext, strlen(ctext));
// Render Bitmap
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
UIImage *returnImage = [UIImage imageWithCGImage:imageMasked];
Because your text drawing mode is kCGTextFillStroke, CGContextShowTextAtPoint is first drawing the fill (and generating a shadow for it), and then drawing the stroke (which gets its own shadow).
To fix it, use a transparency layer.
Note that it will draw much faster if you use CGContextBeginTransparencyLayerWithRect() and pass in as small a rect as you can.
Alternatively: if it's a good enough approximation, you might consider drawing your text in two steps:
fill, with shadow
stroke, with no shadow
If your stroke is small, the difference will not be very perceptible, and it will draw considerably faster.

iPhone - Draw some text with CGContext : ok but... mirrored

When I draw some text using CGContext, it is drawn mirrored.
I tried to apply some transformations, then it is draw well, but then the rest of the drawing and all coordinates seems to be draw bad.
I tried to save and restore the context, before and ater drawing the text (and aplying transformation), but that does not help.
How some text must be drawn onto a View using CGContext without affecting the rest of the drawing, nor the onscreen CGPoint coords for that text ?
Can you clarify what you mean as 'mirrored'? Here is some code for drawing some black text. It should not be 'mirrored'.
CGRect viewBounds = self.bounds;
CGContextTranslateCTM(ctx, 0, viewBounds.size.height);
CGContextScaleCTM(ctx, 1, -1);
CGContextSetRGBFillColor(ctx, 0.0, 1.0, 0.0, 1.0);
CGContextSetLineWidth(ctx, 2.0);
CGContextSelectFont(ctx, "Helvetica", 10.0, kCGEncodingMacRoman);
CGContextSetCharacterSpacing(ctx, 1.7);
CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGContextShowTextAtPoint(ctx, 100.0, 100.0, "SOME TEXT", 9);
I think you have to reverse the text matrix :
CGAffineTransform transform = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, 0.0);
CGContextSetTextMatrix(context, transform);

iPhone: Create CGLayer with gradient point and transparent background

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.

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.