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.
Related
i want to draw 12 images in a circle representing the watch numbers, i have read all topics on stackoverflow regarding images with transparent border but it's not working in my case
-(UIImage *)addImageNumber_:(UIImage *)img {
int w = img.size.width;
int h = img.size.height;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1);
CGContextSetShouldAntialias(context,YES);
CGContextSetAllowsAntialiasing( context ,YES );
CGAffineTransform transform;
for (int x=0; x<=11; x++) {
UIImage *timg1 = [UIImage imageNamed:#"2.png"];
CGRect imageRect = CGRectMake(0, 0, timg1.size.width+2, timg1.size.height+2);
UIGraphicsBeginImageContext(imageRect.size);
[timg1 drawInRect:CGRectMake(1,1,timg1.size.width,timg1.size.height)];
timg1 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
transform = CGAffineTransformIdentity;
CGContextDrawImage(context, CGRectMake((w-26)/2, 0, 26, 30), timg1.CGImage);
transform = CGAffineTransformConcat(transform, CGAffineTransformMakeTranslation(-w/2, -w/2));
transform = CGAffineTransformConcat(transform, CGAffineTransformMakeRotation(radians(30)));
transform = CGAffineTransformConcat(transform, CGAffineTransformMakeTranslation(w/2, w/2));
CGContextConcatCTM(context, transform);
}
CGImageRef imageMasked = CGBitmapContextCreateImage(context);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return [UIImage imageWithCGImage:imageMasked];
}
UIViewEdgeAntialiasing = YES;
UIImage *img = [UIImage imageNamed:#"test.png"];
UIImage *img2 = [self addImageNumber_:img ];
R1.image = img2;
[self.view addSubview:R1];
test img is the background of the watch , 2.png is a transparent png with transparent borders
numbers at 12'o clock and 6'o clock looks ok because they are not rotated the rest are jaggy
Never say UIGraphicsBeginImageContext(imageRect.size). Say UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0). That way, on a double-resolution screen, you'll get a double-resolution graphics context.
You could even try a resolution value of 4 to increase the resolution even further.
Of course, the fact that you're starting with a pre-drawn image of a "2" might limit your resolution; you can't make a silk purse out of a sow's ear. You could be a lot better off drawing the "2" from scratch as a string.
try
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
Swift 3:
context!.interpolationQuality = .high
I tried many many ways to draw a black outline around image.
This is an example of the result I want:
Can someone please let me know how should I do it? or give me an example ?
Edit: i stuck in here: can someone please help me finish it ?
What i did was to make another shape in black color under the the white with shadow and then fill it all in black so it will be like an outline - but i cant figure out how to make the last and important part of making the shadow and fill it to be all in black.
- (IBAction)addStroke:(id)sender{
[iconStrokeTest setImage:[self makeIconStroke:icon.imageView.image]];
}
- (UIImage *)makeIconStroke:(UIImage *)image{
CGImageRef originalImage = [image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate(NULL,
CGImageGetWidth(originalImage),
CGImageGetHeight(originalImage),
8,
CGImageGetWidth(originalImage)*4,
colorSpace,
kCGImageAlphaPremultipliedLast);
CGContextDrawImage(bitmapContext, CGRectMake(0, 0, CGBitmapContextGetWidth(bitmapContext), CGBitmapContextGetHeight(bitmapContext)), originalImage);
CGImageRef finalMaskImage = [self createMaskWithImageAlpha:bitmapContext];
UIImage *result = [UIImage imageWithCGImage:finalMaskImage];
CGContextRelease(bitmapContext);
CGImageRelease(finalMaskImage);
// begin a new image context, to draw our colored image onto
UIGraphicsBeginImageContext(result.size);
// get a reference to that context we created
CGContextRef context = UIGraphicsGetCurrentContext();
// set the fill color
[[UIColor blackColor] setFill];
// translate/flip the graphics context (for transforming from CG* coords to UI* coords
CGContextTranslateCTM(context, 0, result.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// set the blend mode to color burn, and the original image
CGContextSetBlendMode(context, kCGBlendModeColorBurn);
CGRect rect = CGRectMake(0, 0, result.size.width, result.size.height);
CGContextDrawImage(context, rect, result.CGImage);
// set a mask that matches the shape of the image, then draw (color burn) a colored rectangle
CGContextClipToMask(context, rect, result.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;
}
- (CGImageRef)createMaskWithImageAlpha:(CGContextRef)originalImageContext {
UInt8 *data = (UInt8 *)CGBitmapContextGetData(originalImageContext);
float width = CGBitmapContextGetBytesPerRow(originalImageContext) / 4;
float height = CGBitmapContextGetHeight(originalImageContext);
int strideLength = ROUND_UP(width * 1, 4);
unsigned char * alphaData = (unsigned char * )calloc(strideLength * height, 1);
CGContextRef alphaOnlyContext = CGBitmapContextCreate(alphaData,
width,
height,
8,
strideLength,
NULL,
kCGImageAlphaOnly);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
unsigned char val = data[y*(int)width*4 + x*4 + 3];
val = 255 - val;
alphaData[y*strideLength + x] = val;
}
}
CGImageRef alphaMaskImage = CGBitmapContextCreateImage(alphaOnlyContext);
CGContextRelease(alphaOnlyContext);
free(alphaData);
// Make a mask
CGImageRef finalMaskImage = CGImageMaskCreate(CGImageGetWidth(alphaMaskImage),
CGImageGetHeight(alphaMaskImage),
CGImageGetBitsPerComponent(alphaMaskImage),
CGImageGetBitsPerPixel(alphaMaskImage),
CGImageGetBytesPerRow(alphaMaskImage),
CGImageGetDataProvider(alphaMaskImage), NULL, false);
CGImageRelease(alphaMaskImage);
return finalMaskImage;
}
Well, theres no build-in API for that. You'll have to do it yourself or find a libary for this. But you could "fake" the effect by drawing the image with a shadow. Note that shadows can be any color, it doesn't have to look like a shadow. This would be the easiest way.
Other than that you could vectorize the raster image and stroke that path. Core image's edge detection filter will help for that but it could turn out to be hard to acomplish.
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.
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.
I try to draw a string in this texture:
http://picasaweb.google.it/lh/photo/LkYWBv_S_9v2d6BAfbrhag?feat=directlink
but the green numbers seem vertical flipped.
I've created my context in this way:
colorSpace = CGColorSpaceCreateDeviceRGB();
data = malloc(height * width * 4);
context = CGBitmapContextCreate(data, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
and i've draw the strings:
UIGraphicsPushContext(context);
for(int i=0;i<packs.size();++i)
{
CGPoint points[4] =
{
gltextures[i].texCoords[0] * size.width, //0
gltextures[i].texCoords[1] * size.height, //1
gltextures[i].texCoords[2] * size.width, //2
gltextures[i].texCoords[3] * size.height, //3
gltextures[i].texCoords[4] * size.width, //4
gltextures[i].texCoords[5] * size.height, //5
gltextures[i].texCoords[6] * size.width, //6
gltextures[i].texCoords[7] * size.height //7
};
CGRect debugRect = CGRectMake
(
gltextures[i].texCoords[0] * size.width,
gltextures[i].texCoords[1] * size.height,
gltextures[i].width,
gltextures[i].height
);
CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor]);
CGContextAddLines(context, points, 4);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathStroke);
NSString* s = [NSString stringWithFormat:#"%d",gltextures[i].texID];
UIFont* font = [UIFont fontWithName:#"Arial" size:12];
CGContextSetRGBFillColor(context, 0, 1, 0, 1);
[s drawAtPoint:points[0] withFont:font];
}
UIGraphicsPopContext();
The transformation matrix seems the Identity matrix... no CGAffineTransform is applied to THIS context.
If the string is flipped, maybe, all my images are flipped!
Any suggestion?
PS: sorry for my english ;)
As describe here, the coordinate system for a context within a UIView or its layer is inverted when compared to the normal Quartz drawing space. However, when using Quartz to draw to an image, this is no longer the case. The UIStringDrawing Cocoa Touch category on NSString that gives you the -drawAtPoint:withFont: method assumes an inverted layout, and that's why you're seeing the flipped text in your image.
One way to work around this would be to save the graphics state, apply an invert transform to the image, draw the text, and restore the old graphics state. This would look something like the following:
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0f, self.bounds.size.height);
CGContextScaleCTM(context, 1.0f, -1.0f);
[text drawAtPoint:CGPointMake(0.0f, 0.0f) withFont:font];
CGContextRestoreGState(context);
This is a fairly crude example, in that I believe it just draws the text at the bottom of the image, but you should be able to adjust the translation or the coordinate of the text to place it where it's needed.
Better save the text 's matrix before draw, otherwise it will affect other text draw by CoreGraphic:
CGContextSaveGState(ctx);
CGAffineTransform save = CGContextGetTextMatrix(ctx);
CGContextTranslateCTM(ctx, 0.0f, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0f, -1.0f);
[str drawAtPoint:point withFont:font];
CGContextSetTextMatrix(ctx, save);
CGContextRestoreGState(ctx);