iphone: Apply a texture to CGContextStrokePath
I search all the question and answer in StackOverflow but can not get the result I want. I would like to use a PNG file, to display as a stroke at the point user touch and move. This is the PNG file:
But I did all the ways on StackOverflow, even using CGPatternRef, but I don't have the result I want. The result I have is here:开发者_StackOverflow社区
But the result I want to have is here:
This result I have done in OpenGL ES, but I can not do that in Core Graphics. I think I can use CGContextDrawImage at each point, but this way cause poor performance because I must draw image all the points between 2 points user touched. I think you have encountered this problem before. Do you think I can apply the texture to draw stroke like this in Core Graphics? I am very appreciated.
When you create a line using a pattern it is going to look like the first image, not the second. The line is simply a tiling of the texture image you provide cropped out into the shape of a line. There is no way for it to know that you meant to merge the circles together to look like a solid line. For that you should probably just make a line with a solid black color.
For anyone trying to achieve the patterned line in the first image there is an excellent tutorial explaining how to do this here: http://blog.robertturrall.com/tag/cgcontextsetstrokepattern/
Essentially what you need is:
// first define the pattern width and height
const float kPatternWidth = 8;
const float kPatternHeight = 8;
void DrawPatternCellCallback(void *info, CGContextRef cgContext)
{
// Create a CGImage and use CGContextDrawImage() to draw it into the graphics context provided by the callback function.
UIImage *patternImage = [UIImage imageNamed:@"yourtextureimage.png"];
CGContextDrawImage(cgContext, CGRectMake(0, 0, kPatternWidth, kPatternHeight), patternImage.CGImage);
}
Make sure "void" has no parentheses around it. Also make sure the DrawPatternCellCallback part comes before drawRect or it won't work.
Then your drawRect needs the following:
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
const CGRect patternBounds = CGRectMake(0, 0, kPatternWidth, kPatternHeight);
const CGPatternCallbacks kPatternCallbacks = {0, DrawPatternCellCallback, NULL};
CGAffineTransform patternTransform = CGAffineTransformIdentity;
CGPatternRef strokePattern = CGPatternCreate(
NULL,
patternBounds,
patternTransform,
kPatternWidth, // horizontal spacing
kPatternHeight, // vertical spacing
kCGPatternTilingNoDistortion,
true,
&kPatternCallbacks);
CGFloat color1[] = {1.0, 1.0, 1.0, 1.0};
CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
CGContextSetStrokeColorSpace(ctx, patternSpace);
CGContextSetStrokePattern(ctx, strokePattern, color1);
CGContextSetLineWidth(ctx, 4.0);
CGContextDrawPath(ctx);
// If you aren't using ARC:
CGPatternRelease(strokePattern);
strokePattern = NULL;
CGColorSpaceRelease(patternSpace);
patternSpace = NULL;
}
critical ...
if you're doing the common thing of drawing a blurred version of the image over itself, for example.
You will almost certainly need code like this ...
float isf;
isf = [[UIScreen mainScreen] bounds].size.width / wholeImageWidth;
(that's if you're drawing fullscreen; adjust in the obvious way if drawing in a window) where wholeImageWidth is the real width of the image you're editing.
then when you create the stroke pattern you use that like this
CGAffineTransformScale( CGAffineTransformIdentity, isf, -isf)
but noting that annoyingly you may have to reotate it differently for the camera and album. So something like.......
if ( self.wasFromCameraNotAlbum )
{
strokePattern = CGPatternCreate(
NULL,
CGRectMake(0,0,wholeWidth,wholeHeight),
CGAffineTransformConcat(
CGAffineTransformScale( CGAffineTransformIdentity, isf, -isf),
CGAffineTransformRotate( CGAffineTransformIdentity, 90*M_PI/180 )
),
wholeWidth,wholeHeight,
kCGPatternTilingNoDistortion, true, &kPatternCallbacks);
}
else
{
strokePattern = CGPatternCreate(
NULL,
CGRectMake(0,0,wholeWidth,wholeHeight),
CGAffineTransformScale( CGAffineTransformIdentity, isf, -isf),
wholeWidth,wholeHeight,
kCGPatternTilingNoDistortion, true, &kPatternCallbacks);
}
For googling, this is due to the camera, rotation, iPhone 6+, iOS8, album.
Since llama591's answer was so awesome. Here's some sample code that may just help someone someday. This is the simplest possible code I can write for pattern drawing:
This works fine it Xcode5/iOS7 (it's January 2014):
Here's some very typical drawing code that draws BLUE LINES:
-(void)drawRect:(CGRect)rect
{
[curImage drawAtPoint:CGPointMake(0, 0)];
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context,
previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 20.0);
CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
CGContextStrokePath(context);
[super drawRect:rect];
}
Notice the call CGContextSetStrokeColorWithColor which simply sets the line to be a solid line, in this case blue.
Next here is the identical code. But, we replace that one line of code, with, all the code for drawing a pattern. (And there is also a new separate callback routine, which is trivial.)
For clarity here is only the 15 lines of code, that will replace the single simple StrokeColor line of code:
const CGPatternCallbacks kPatternCallbacks = {0, simplePatternCallback, NULL};
CGFloat color1[] = {1.0, 1.0, 1.0, 1.0};
CGPatternRef strokePattern = CGPatternCreate(
NULL,
CGRectMake(0,0,100,100),
CGAffineTransformIdentity,
100,100,
kCGPatternTilingNoDistortion,
true,
&kPatternCallbacks);
CGContextSetStrokeColorSpace(UIGraphicsGetCurrentContext(),
CGColorSpaceCreatePattern(NULL););
CGContextSetStrokePattern(context, strokePattern, color1);
CGPatternRelease(strokePattern);
strokePattern = NULL;
The pattern should be a PNG in your project, which could be say 100x100 pixels, here called "pattern100100.png".
Again, I replaced the 1 line of code with the 15 lines of code for the pattern. (And there's also the separate trivial callback function.) Here's the whole thing:
-(void)drawRect:(CGRect)rect
{
[curImage drawAtPoint:CGPointMake(0, 0)];
CGPoint mid1 = midPoint(previousPoint1, previousPoint2);
CGPoint mid2 = midPoint(currentPoint, previousPoint1);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
CGContextMoveToPoint(context, mid1.x, mid1.y);
CGContextAddQuadCurveToPoint(context,
previousPoint1.x, previousPoint1.y, mid2.x, mid2.y);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, 20.0);
const CGPatternCallbacks kPatternCallbacks = {0, simplePatternCallback, NULL};
CGFloat color1[] = {1.0, 1.0, 1.0, 1.0};
CGPatternRef strokePattern = CGPatternCreate(
NULL,
CGRectMake(0,0,100,100),
CGAffineTransformIdentity,
100,100,
kCGPatternTilingNoDistortion,
true,
&kPatternCallbacks);
CGContextSetStrokeColorSpace(UIGraphicsGetCurrentContext(),
CGColorSpaceCreatePattern(NULL););
CGContextSetStrokePattern(context, strokePattern, color1);
CGPatternRelease(strokePattern);
strokePattern = NULL;
CGContextStrokePath(context);
[super drawRect:rect];
}
void simplePatternCallback(void *info, CGContextRef ctx)
{
UIImage *brushTexture = [UIImage imageNamed:@"pattern100100.png"];
CGContextDrawImage(ctx, CGRectMake(0, 0, 100,100), brushTexture.CGImage);
}
A couple of useful comments to include in the code!
// re the final argument:
// from Apple doco: "If the specified pattern is a 'colored' pattern,
// pass an alpha value."
// in fact you have to pass in an array of four values.
// note, "stencil patterns" (aka "uncolored patterns") patterns
// are basically masks, rarely used
// re the "true" in CGPatternCreate, that means "coloured pattern"
// (ie not "stencil pattern")
I really hope it helps someone.
NOTE: whenever you see code examples like this (even at Apple). They always do the whole thing "inside the drawRect". Notice that "strokePattern" is indeed created every time drawRect is called! To me this seems crazy, I always simply use a variable or property for "strokePattern" and the other elements. Similarly it would be ridiculous to use imageNamed every time in the callback, simply use a property you previously prepare.
Again I really hope it helps someone. Cheers
useful footnote: in CGPaternCreate, the example code shows CGAffineTransformIdentity for argument three.
Often, you would be using some photo or other dynamic image. In that case, almost certainly it would be ...
CGAffineTransformScale(CGAffineTransformIdentity,1,-1),
to take care of the upside down scale in core graphics. Hope it helps.
Thanks for the link, llama591.
Haisergeant, if what you're looking for is a nice thick line using the texture you had in your first example then you'll need to play around with the kPatternWidth and kPatternHeight values, aswell as the value you pass through in the line CGContextSetLineWidth(ctx, 4.0);.
I also had to modify the texture image I used to ensure that there was very little whitespace on it's edges as this will introduce white gaps in the line, as shown in your example.
精彩评论