4

I'm trying to draw squiggle in Set Card Game. The output is not even close to what I want. How should I improve the drawing?

  • Please ignore the shading in "What I want" picture. I have figured out how to do that. The question is how to drawing the shape of squiggle
  • The purpose of this is to practice drawing with objective-c. So just using png picture is not a solution here...

..

#define SQUIGGLE_CURVE_FACTOR 0.5
 - (UIBezierPath *)drawSquiggleAtPoint:(CGPoint)point
{
    CGFloat dx = self.bounds.size.width * SYMBOL_WIDTH_RATIO / 2;
    CGFloat dy = self.bounds.size.height * SYMBOL_HEIGHT_RATIO / 2;

    UIBezierPath *path = [[UIBezierPath alloc] init];
    CGFloat dsqx = dx * SQUIGGLE_CURVE_FACTOR;
    CGFloat dsqy = dy * SQUIGGLE_CURVE_FACTOR;
    [path moveToPoint:CGPointMake(point.x - dx, point.y)];
    [path addQuadCurveToPoint:CGPointMake(point.x - dsqx, point.y + dsqy) controlPoint:CGPointMake(point.x - dx, point.y + dy + dsqy)];
    [path addCurveToPoint:CGPointMake(point.x + dx, point.y) controlPoint1:point controlPoint2:CGPointMake(point.x + dx, point.y + dy * 2)];
    [path addQuadCurveToPoint:CGPointMake(point.x + dsqx, point.y - dsqy) controlPoint:CGPointMake(point.x + dx, point.y - dy - dsqy)];
    [path addCurveToPoint:CGPointMake(point.x - dx, point.y) controlPoint1:point controlPoint2:CGPointMake(point.x - dx, point.y - dy * 2)];

    return path;
}
  • What I got:

What I got

  • What I want:

What I want


final version

- (UIBezierPath *)drawSquiggleAtPoint:(CGPoint)point
{
    CGSize size = CGSizeMake(self.bounds.size.width * SYMBOL_WIDTH_RATIO, self.bounds.size.height * SYMBOL_HEIGHT_RATIO);

    UIBezierPath *path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(104, 15)];
    [path addCurveToPoint:CGPointMake(63, 54) controlPoint1:CGPointMake(112.4, 36.9) controlPoint2:CGPointMake(89.7, 60.8)];
    [path addCurveToPoint:CGPointMake(27, 53) controlPoint1:CGPointMake(52.3, 51.3) controlPoint2:CGPointMake(42.2, 42)];
    [path addCurveToPoint:CGPointMake(5, 40) controlPoint1:CGPointMake(9.6, 65.6) controlPoint2:CGPointMake(5.4, 58.3)];
    [path addCurveToPoint:CGPointMake(36, 12) controlPoint1:CGPointMake(4.6, 22) controlPoint2:CGPointMake(19.1, 9.7)];
    [path addCurveToPoint:CGPointMake(89, 14) controlPoint1:CGPointMake(59.2, 15.2) controlPoint2:CGPointMake(61.9, 31.5)];
    [path addCurveToPoint:CGPointMake(104, 15) controlPoint1:CGPointMake(95.3, 10) controlPoint2:CGPointMake(100.9, 6.9)];

    [path applyTransform:CGAffineTransformMakeScale(0.9524*size.width/100, 0.9524*size.height/50)];
    [path applyTransform:CGAffineTransformMakeTranslation(point.x - size.width/2 - 3 * size.width /100, point.y - size.height/2 - 8 * size.height/50)];

    return path;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
dumduke
  • 471
  • 4
  • 15
  • 1
    How many different squiggles are there? Seems like it would be easier just to add some .png files to the project where each .png is a different squiggle. – user3386109 Aug 19 '14 at 16:06
  • @user3386109 Thanks for the advice. The purpose of this is to practice drawing in iOS. I'll the question to clarify this. – dumduke Aug 19 '14 at 16:18
  • You probably want to scale both x and y based on either the smallest or largest dimension so you don't throw off the proportions. – Joshua Olson Dec 29 '19 at 00:25

5 Answers5

5

Here's some code that generates a fairly decent rendition of a squiggle

- (void)drawSquiggle
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    // translate and scale the squiggle
    CGContextSaveGState( context );
    CGContextTranslateCTM( context, 0, 100 );
    CGContextScaleCTM( context, 2.0, 2.0 );

    // create the squiggle path
    CGContextMoveToPoint( context, 104.0, 15.0 );
    CGContextAddCurveToPoint( context, 112.4 , 36.9,   89.7,  60.8,   63.0,  54.0 );
    CGContextAddCurveToPoint( context,  52.3 , 51.3,   42.2,  42.0,   27.0,  53.0 );
    CGContextAddCurveToPoint( context,   9.6 , 65.6,    5.4,  58.3,    5.0,  40.0 );
    CGContextAddCurveToPoint( context,   4.6 , 22.0,   19.1,   9.7,   36.0,  12.0 );
    CGContextAddCurveToPoint( context,  59.2 , 15.2,   61.9,  31.5,   89.0,  14.0 );
    CGContextAddCurveToPoint( context,  95.3 , 10.0,  100.9,   6.9,  104.0,  15.0 );

    // draw the squiggle
    CGContextSetLineCap( context, kCGLineCapRound );
    CGContextSetLineWidth( context, 2.0 );
    CGContextStrokePath( context );

    // restore the graphics state
    CGContextRestoreGState( context );
}

This is what it produces

enter image description here


Scaling and Translating the Squiggle

There are two ways to change the size and position of the squiggle.

One way is to change all the number that are passed to the CGContextAddCurveToPoint function. For example, if you adjust the x values with x = (x-3) * 0.9524 and adjust all the y values with y = (y-8) * 0.9524, then the squiggle fits nicely into a 100x50 rectangle.

The other way is to change the affine transform of your drawing context before drawing the squiggle. You can apply translation and scale transformations to place/size the squiggle wherever you want it. Note that the order in which the transformations are applied is important. Also, you can save and restore the graphics state so that the transformations are only applied to the squiggle, and not other items that you are drawing. The code above moves the squiggle 100 pixels down, and makes it twice as big.

user3386109
  • 34,287
  • 7
  • 49
  • 68
  • Thanks for the codes... I voted up. Actually I need an algorithm, which should based on a given point and also the card size. So I can draw multiple (two or three) squiggles on one card. – dumduke Aug 20 '14 at 05:23
  • @CaffHuang I modified the answer with some notes about scaling and translating the squiggle. Note that the code snippet has been changed to demonstrate the concepts. – user3386109 Aug 20 '14 at 16:06
  • Answered taken. Thanks so much for your help. I've updated my codes using UIBezierPath to the original code. applyTransform really helped. – dumduke Aug 21 '14 at 06:33
3

My squiggle looks like this. Unfortunately I can't insert image directly

This is my method for this. And squiggle scale with bounds of CGrect

typedef enum : NSUInteger
{
solidShading=1,
stripedShading=2,
openShading=3,
numShading=4,
} cardShading;


-(void)drawSquiggleInBounds:(CGRect)bounds withColor:(UIColor *)color withShading:(cardShading)shading
{
if (shading > 3) {return;}

UIBezierPath *path = [[UIBezierPath alloc] init];

[color setStroke];

path.lineWidth = 2;

[path moveToPoint:CGPointMake(bounds.origin.x + bounds.size.width*0.05, bounds.origin.y + bounds.size.height*0.40)];

[path addCurveToPoint:CGPointMake(bounds.origin.x + bounds.size.width*0.35, bounds.origin.y + bounds.size.height*0.25)
        controlPoint1:CGPointMake(bounds.origin.x + bounds.size.width*0.09, bounds.origin.y + bounds.size.height*0.15)
        controlPoint2:CGPointMake(bounds.origin.x + bounds.size.width*0.18, bounds.origin.y + bounds.size.height*0.10)];

[path addCurveToPoint:CGPointMake(bounds.origin.x + bounds.size.width*0.75, bounds.origin.y + bounds.size.height*0.30)
        controlPoint1:CGPointMake(bounds.origin.x + bounds.size.width*0.40, bounds.origin.y + bounds.size.height*0.30)
        controlPoint2:CGPointMake(bounds.origin.x + bounds.size.width*0.60, bounds.origin.y + bounds.size.height*0.45)];

[path addCurveToPoint:CGPointMake(bounds.origin.x + bounds.size.width*0.97, bounds.origin.y + bounds.size.height*0.35)
        controlPoint1:CGPointMake(bounds.origin.x + bounds.size.width*0.87, bounds.origin.y + bounds.size.height*0.15)
        controlPoint2:CGPointMake(bounds.origin.x + bounds.size.width*0.98, bounds.origin.y + bounds.size.height*0.00)];

[path addCurveToPoint:CGPointMake(bounds.origin.x + bounds.size.width*0.45, bounds.origin.y + bounds.size.height*0.85)
        controlPoint1:CGPointMake(bounds.origin.x + bounds.size.width*0.95, bounds.origin.y + bounds.size.height*1.10)
        controlPoint2:CGPointMake(bounds.origin.x + bounds.size.width*0.50, bounds.origin.y + bounds.size.height*0.95)];

[path addCurveToPoint:CGPointMake(bounds.origin.x + bounds.size.width*0.25, bounds.origin.y + bounds.size.height*0.85)
        controlPoint1:CGPointMake(bounds.origin.x + bounds.size.width*0.40, bounds.origin.y + bounds.size.height*0.80)
        controlPoint2:CGPointMake(bounds.origin.x + bounds.size.width*0.35, bounds.origin.y + bounds.size.height*0.75)];

[path addCurveToPoint:CGPointMake(bounds.origin.x + bounds.size.width*0.05, bounds.origin.y + bounds.size.height*0.40)
        controlPoint1:CGPointMake(bounds.origin.x + bounds.size.width*0.00, bounds.origin.y + bounds.size.height*1.10)
        controlPoint2:CGPointMake(bounds.origin.x + bounds.size.width*0.005, bounds.origin.y + bounds.size.height*0.60)];

[path closePath];

[self drawShading:shading withColor:color inPath:path];

[path stroke];
}

Also my methods for shading:

#define DISTANCE_BETWEEN_STRIPES 4

-(void)drawShading:(cardShading)shading withColor:(UIColor *)color inPath:(UIBezierPath *)beziePath
{
switch (shading)
{
    case 1:
        [color setFill];
        [beziePath fill];
        break;
    case 2:
        [self drawStripedShadingForPath:beziePath];
        break;
    default:
        break;
}
}

-(void)drawStripedShadingForPath:(UIBezierPath *)pathOfSymbol
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);

CGRect bounds = [pathOfSymbol bounds];
UIBezierPath *path = [[UIBezierPath alloc] init];

for (int i = 0; i < bounds.size.width; i += DISTANCE_BETWEEN_STRIPES)
{
    [path moveToPoint:CGPointMake(bounds.origin.x + i, bounds.origin.y)];
    [path addLineToPoint:CGPointMake(bounds.origin.x + i, bounds.origin.y + bounds.size.height)];
}

[pathOfSymbol addClip];

[path stroke];

CGContextRestoreGState(UIGraphicsGetCurrentContext());
}
1

Here is a SwiftUI version using the path curves from @user3386109 (thanks for making the perfect squiggle). I also added an offset and transform to position the squiggle in the center of its bounding view and span the entire width of its view.

import SwiftUI

struct SquiggleShape: Shape {
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
                
        path.move(to: CGPoint(x: 104.0, y: 15.0))
        path.addCurve(to: CGPoint(x: 63.0, y: 54.0),
                      control1: CGPoint(x: 112.4, y: 36.9),
                      control2: CGPoint(x: 89.7, y: 60.8))
        path.addCurve(to: CGPoint(x: 27.0, y: 53.0),
                      control1: CGPoint(x: 52.3, y: 51.3),
                      control2: CGPoint(x: 42.2, y: 42.0))
        path.addCurve(to: CGPoint(x: 5.0, y: 40.0),
                      control1: CGPoint(x: 9.6, y: 65.6),
                      control2: CGPoint(x: 5.4, y: 58.3))
        path.addCurve(to: CGPoint(x: 36.0, y: 12.0),
                      control1: CGPoint(x: 4.6, y: 22.0),
                      control2: CGPoint(x: 19.1, y: 9.7))
        path.addCurve(to: CGPoint(x: 89.0, y: 14.0),
                      control1: CGPoint(x: 59.2, y: 15.2),
                      control2: CGPoint(x: 61.9, y: 31.5))
        path.addCurve(to: CGPoint(x: 104.0, y: 15.0),
                      control1: CGPoint(x: 95.3, y: 10.0),
                      control2: CGPoint(x: 100.9, y: 6.9))
        
        let pathRect = path.boundingRect
        path = path.offsetBy(dx: rect.minX - pathRect.minX, dy: rect.minY - pathRect.minY)
        
        let scale: CGFloat = rect.width / pathRect.width
        let transform = CGAffineTransform(scaleX: scale, y: scale)
        path = path.applying(transform)
        
        
        return path
            .offsetBy(dx: rect.minX - path.boundingRect.minX, dy: rect.midY - path.boundingRect.midY)
    }
}

Squiggle

de3z1e
  • 411
  • 4
  • 8
0

I drew this in Illustrator and then moved it to swift. Here's the squiggle I was able to create. It should be trivial to convert it to objective-C if you look at questioner's final solution.

    func getSquiggle(_ bounds: CGRect) -> UIBezierPath {
    // Based on: https://stackoverflow.com/questions/25387940/how-to-draw-a-perfect-squiggle-in-set-card-game-with-objective-c
    let startPoint = CGPoint(x: 76.5, y: 403.5)
    let curves = [ // to, cp1, cp2
        (CGPoint(x:  199.5, y: 295.5), CGPoint(x: 92.463, y: 380.439),
                                       CGPoint(x: 130.171, y: 327.357)),
        (CGPoint(x:  815.5, y: 351.5), CGPoint(x: 418.604, y: 194.822),
                                       CGPoint(x: 631.633, y: 454.052)),
        (CGPoint(x: 1010.5, y: 248.5), CGPoint(x: 844.515, y: 313.007),
                                       CGPoint(x: 937.865, y: 229.987)),
        (CGPoint(x: 1057.5, y: 276.5), CGPoint(x: 1035.564, y: 254.888),
                                       CGPoint(x: 1051.46, y: 270.444)),
        (CGPoint(x:  993.5, y: 665.5), CGPoint(x: 1134.423, y: 353.627),
                                       CGPoint(x: 1105.444, y: 556.041)),
        (CGPoint(x:  860.5, y: 742.5), CGPoint(x: 983.56, y: 675.219),
                                       CGPoint(x: 941.404, y: 715.067)),
        (CGPoint(x:  271.5, y: 728.5), CGPoint(x: 608.267, y: 828.077),
                                       CGPoint(x: 452.192, y: 632.571)),
        (CGPoint(x:  101.5, y: 803.5), CGPoint(x: 207.927, y: 762.251),
                                       CGPoint(x: 156.106, y: 824.214)),
        (CGPoint(x:   49.5, y: 745.5), CGPoint(x: 95.664, y: 801.286),
                                       CGPoint(x: 73.211, y: 791.836)),
        (startPoint, CGPoint(x: 1.465, y: 651.628),
                     CGPoint(x: 1.928, y: 511.233)),
    ]

    // Draw the squiggle
    let path = UIBezierPath()
    path.move(to: startPoint)
    for (to, cp1, cp2) in curves {
        path.addCurve(to: to, controlPoint1: cp1, controlPoint2: cp2)
    }
    path.close()
    // Your code to scale, rotate and translate the squiggle

    return path

My squiggle

Joshua Olson
  • 3,675
  • 3
  • 27
  • 30
0

I'm not familiar with the formal definition of a "perfect" squiggle, in any domain? I am aware that the Set Card Game is, of course, set, but it's hardly perfect.

Geoff Nixon
  • 4,697
  • 2
  • 28
  • 34