Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Drawing a pattern along a path

My goal is to take a pattern like this

enter image description here

and draw it repeatedly along a circular path to produce something similar to this image:

enter image description here

I found several code examples in other questions and an full demo project here but the result is this:

enter image description here

I think the difference between the two images is obvious, but I find it hard to describe (pardon my lack of graphics vocabulary). The result seems to be tiling without the desired rotation/deformation of the pattern. I think I can live with the lack of deformation, but the rotation is key. I think that perhaps the draw callback could/should be modified to include a rotation, but can't figure out how to retrieve/determine the angle at the point of the callback.

I considered an approach where I manually deformed/rotated the image and drew it several times around a centerpoint to achieve the effect I want, but I believe that CoreGraphics could do it with more efficiency and with less code.

Any suggestions about how to achieve the result I want would be appreciated.

Here is the relevant code from the ChalkCircle project:

const float kPatternWidth = 8;
const float kPatternHeight = 8;

void DrawPatternCellCallback(void *info, CGContextRef cgContext)
{
UIImage *patternImage = [UIImage imageNamed:@"chalk_brush.png"];
CGContextDrawImage(cgContext, CGRectMake(0, 0, kPatternWidth, kPatternHeight), patternImage.CGImage);
}

- (void)drawRect:(CGRect)rect {
float startDeg = 0; // where to start drawing
float endDeg = 360; // where to stop drawing
int x = self.center.x;
int y = self.center.y;
int radius = (self.bounds.size.width > self.bounds.size.height ? self.bounds.size.height : self.bounds.size.width) / 2 * 0.8;
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);


CGContextMoveToPoint(ctx, x, y - radius);
CGContextAddArc(ctx, x, y, radius, (startDeg-90)*M_PI/180.0, (endDeg-90)*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextDrawPath(ctx, kCGPathStroke);


CGPatternRelease(strokePattern);
strokePattern = NULL;
CGColorSpaceRelease(patternSpace);
patternSpace = NULL;

}

.SOLUTION FROM SAM

I modified sam's solution to handle non-square patterns, center the result, and remove hard coded numbers by calculating them from the passed in image:

#define MAX_CIRCLE_DIAMETER   290.0f
#define OVERLAP 1.5f

-(void) drawInCircle:(UIImage *)patternImage
{
    int numberOfImages = 12;
    float diameter =  (MAX_CIRCLE_DIAMETER * numberOfImages * patternImage.size.width) / ( (2.0 * M_PI * patternImage.size.height) + (numberOfImages * patternImage.size.width));

    //get the radius, circumference and image size
    CGRect replicatorFrame = CGRectMake((320-diameter)/2.0f, 60.0f, diameter, diameter);
    float radius = diameter/2;
    float circumference = M_PI * diameter;
    float imageWidth = circumference/numberOfImages;
    float imageHeight = imageWidth *  patternImage.size.height / patternImage.size.width;

    //create a replicator layer and add it to our view
    CAReplicatorLayer *replicator = [CAReplicatorLayer layer];

    replicator.frame = replicatorFrame;
    [self.view.layer addSublayer:replicator];

    //configure the replicator
    replicator.instanceCount = numberOfImages;

    //apply a rotation transform for each instance
    CATransform3D transform = CATransform3DIdentity;
    transform = CATransform3DRotate(transform, M_PI / (numberOfImages/2), 0, 0, 1);
    replicator.instanceTransform = transform;

    //create a sublayer and place it inside the replicator
    CALayer *layer = [CALayer layer];
    //the frame places the layer in the middle of the replicator layer and on the outside of
    //the replicator layer so that the the size is accurate relative to the circumference
    layer.frame = CGRectMake(radius - (imageWidth/2.0) - (OVERLAP/2.0), -imageHeight/2.0, imageWidth+OVERLAP, imageHeight);

    layer.anchorPoint = CGPointMake(0.5, 1);
    [replicator addSublayer:layer];

    //apply a perspective transform to the layer
    CATransform3D perspectiveTransform = CATransform3DIdentity;
    perspectiveTransform.m34 = 1.0f / -radius;
    perspectiveTransform = CATransform3DRotate(perspectiveTransform, (M_PI_4), -1, 0, 0);
    layer.transform = perspectiveTransform;

    //set the image as the layer's contents
    layer.contents = (__bridge id)patternImage.CGImage;
}
like image 996
software evolved Avatar asked Mar 13 '14 17:03

software evolved


1 Answers

Using Core Animation's replicator layer, I managed to create this result: enter image description here

I think it's close to what your looking for. In this example all the images are square with a 3d X rotation applied to each of them.

#import <QuartzCore/QuartzCore.h>    


//set the number of images and the diameter (width) of the circle
int numberOfImages = 30;
float diameter = 450.0f;

//get the radius, circumference and image size
float radius = diameter/2;
float circumference = M_PI * diameter;
float imageSize = circumference/numberOfImages;

//create a replicator layer and add it to our view
CAReplicatorLayer *replicator = [CAReplicatorLayer layer];
replicator.frame = CGRectMake(100.0f, 100.0f, diameter, diameter);
[self.view.layer addSublayer:replicator];

//configure the replicator
replicator.instanceCount = numberOfImages;

//apply a rotation transform for each instance
CATransform3D transform = CATransform3DIdentity;
transform = CATransform3DRotate(transform, M_PI / (numberOfImages/2), 0, 0, 1);
replicator.instanceTransform = transform;

//create a sublayer and place it inside the replicator
CALayer *layer = [CALayer layer];
//the frame places the layer in the middle of the replicator layer and on the outside of the replicator layer so that the the size is accurate relative to the circumference
layer.frame = CGRectMake(radius - (imageSize/2), -imageSize/2, imageSize, imageSize);
layer.anchorPoint = CGPointMake(0.5, 1);
[replicator addSublayer:layer];

//apply a perspective transofrm to the layer
CATransform3D perspectiveTransform = CATransform3DIdentity;
perspectiveTransform.m34 = 1.0f / -radius;
perspectiveTransform = CATransform3DRotate(perspectiveTransform, (M_PI_4), -1, 0, 0);
layer.transform = perspectiveTransform;

//set the image as the layer's contents
layer.contents = (__bridge id)[UIImage imageNamed:@"WCR3Q"].CGImage;
like image 66
Sam Avatar answered Oct 18 '22 13:10

Sam