Challenge: Colors - My Solution


#1

So I think I figured out a clever way to generate the random colors of the arcs, however I wanted to post it because I wanted to get others opinions. Also, I was looking to see if there was a better way of getting a random float between 0 and 1 inclusive.

If you do not want to know a valid solution (even if it isn’t the most elegant) please stop reading.

How I thought if this was taking the book’s advice and looking up the documentation of UIColor. I found an init method (among others)

- (UIColor *)initWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha

The documentation that the variables red, green, blue, and alpha should be a float between 0.0 and 1.0 inclusive. Values below 0.0 are treated as 0.0 and values above 1.0 are treated as 1.0.

Anyhow, I did a google search and I found a way of getting a random float between 0.0 and 1.0

At the top of HypnosisView.m I defined a constant

#define ARC4RANDOM_MAX      0x100000000

Then I moved the setStroke message into the for loop which actually draws the circles and added some code. Here’s my changed for loop:

for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
    {
        // New code starts here
        double red = ((double)arc4random() / ARC4RANDOM_MAX);
        double green = ((double)arc4random() / ARC4RANDOM_MAX);
        double blue = ((double)arc4random() / ARC4RANDOM_MAX);
        double alpha = ((double)arc4random() / ARC4RANDOM_MAX);

        [[[UIColor alloc] initWithRed:red green:green blue:blue alpha:alpha] setStroke];
        
        // End of new code
        CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
        CGContextStrokePath(context);
    }

It actually looks pretty cool (in my opinion). Please let me know if anybody else has an easier way of doing this (especially the random 0.0 - 1.0 part)


#2

I got on a very similar track and was happy to come here to check and find your answer. It’s exactly the same logic but with slight differences in the color variables type and in the method used to set the new color:

// Draw concentric circles from the outside in
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
    {
        CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI * 2, YES);
        CGContextStrokePath(context);
        
       // New Code Here - Attribute a new random color to the UIColor object
        CGFloat red = (float)random()/RAND_MAX;
        CGFloat green = (float)random()/RAND_MAX;
        CGFloat blue = (float)random()/RAND_MAX;
        CGFloat alpha = 1;
        
        [[UIColor colorWithRed:red green:green blue:blue alpha:alpha] setStroke];
   }

The main difference that intrigues me is that you created a UIColor object with alloc and used the method initWithRed:green:blue:alpha while I used the method colorWithRed:green:blue:alpha.

I think they are equivalent ways of doing the same thing but honestly not sure. I wonder if someone could comment on that.

Thanks


#3

Hi,

alloc / initWithRed:green:blue:alpha is an instance method and if you alloc something then it is your responsibility to release it.

whereas the class method UIColor colorWithRed:green:blue:alpha is autoreleased for you.

The first example is leaking.

Gareth


#4

here mine using the hue color space

float i =0; for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -=20) { [[UIColor colorWithHue:i saturation:1.0 brightness:1.0 alpha:1.0] setStroke]; CGContextAddArc(context, center.x, center.y, currentRadius,0,2.0 * M_PI, YES); CGContextStrokePath(context); if(i > 1.0) i = 0.0; else i = i + 0.1; }


#5

Here’s my approach

I deleted the line that sets the stroke color using UIKit’s UIColor class, and placed an equivalent (I think) Core Graphics stroke color method inside the for loop with randomized color values.

// [[UIColor lightGrayColor] setStroke];   // delete this line

// Draw concentric circles from the outside in
for(float currentRadius = maxRadius; currentRadius > 0; currentRadius -=20) // start big and go smaller each iteration
{
    CGContextSetRGBStrokeColor(context,  (float)random()/RAND_MAX,  (float)random()/RAND_MAX,  (float)random()/RAND_MAX,  1.0 ); 
  
    CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, -M_PI * 2.0, YES);
    CGContextStrokePath(context);      
}

#6

Note that arc4random() generates cryptographic-quality random numbers.

More “true randomness” means slower, which means more battery drain. random() is probably better suited for this. Even the least-true-random rand() function may be sufficient; the random() man page says it’s about 2/3 the speed of rand().


#7

Just to toss in yet another possibility…

I’d always wondered how to go through colours in “rainbow order,” and this challenge provided the excuse to learn that. I found that this blog post explained it very clearly.

So with that in mind, picking up after where center is defined…

    CGContextSetLineWidth( context, 10 );
    
    double frequency = 0.3;
    double amplitude = 0.5;
    double waveCenter = 0.5;
    int iteration = -1;
    float alpha = 0.8;
    
    for( float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 14.0 ) {
        
        ++iteration;
        float red   = (float)( sin( frequency * iteration + 0.0 ) * amplitude + waveCenter );
        float green = (float)( sin( frequency * iteration + 2.0 ) * amplitude + waveCenter );
        float blue  = (float)( sin( frequency * iteration + 4.0 ) * amplitude + waveCenter );
        
        CGContextSetRGBStrokeColor( context, red, green, blue, alpha );
        
        CGContextAddArc( context, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES );
        
        CGContextStrokePath( context );
    }

FWIW.


#8

Here’s the way I did a rainbow:

[code] CGContextSetLineWidth(context, 10);
// [[UIColor lightGrayColor] setStroke]; move inside for loop

// Draw concentric circles from outside in
CGFloat hue;
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)  {
    
    hue = currentRadius /  maxRadius;
    
    [[UIColor colorWithHue: hue saturation: 1.0 brightness: 1.0 alpha: 1.0] setStroke];
    
    CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
    CGContextStrokePath(context);  
}[/code]

Hue is the “shade” of a color going round a color wheel. This method varies it from 1 to 0, one trip around the wheel, as the circles are drawn from edge to center of the window.
edit: subbed maxRadius for the denominator in the hue calculation


#9

Cool — thank you for sharing!


#10

Hello,

I did it in a different way (reusing chapter 1). I thought it may be interesting too :

In HypnosisView.h

@interface HypnosisView : UIView
{
    int currentColorIndex;
    NSMutableArray *colors;   
}
@end

In HypnosisView.m

- (void)drawRect:(CGRect)rect
{
    // Create an array and make the pointer points to it
    colors = [[NSMutableArray alloc] init];
    
    // Add colors to the array
    [colors addObject:[UIColor blackColor]];
    [colors addObject:[UIColor darkGrayColor]];
    [colors addObject:[UIColor lightGrayColor]];
    [colors addObject:[UIColor grayColor]];
    [colors addObject:[UIColor redColor]];
    [colors addObject:[UIColor greenColor]];
    [colors addObject:[UIColor blueColor]];
    [colors addObject:[UIColor cyanColor]];
    [colors addObject:[UIColor yellowColor]];
    [colors addObject:[UIColor magentaColor]];
    [colors addObject:[UIColor orangeColor]];
    [colors addObject:[UIColor purpleColor]];
    [colors addObject:[UIColor brownColor]];

...same code as in the book until

    // Draw concentric circles from the outside in
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
    {
        // Step to the next color
        currentColorIndex++;
        
        // Am I past the last color?
        if (currentColorIndex == [colors count]) {

            NSLog(@"[colors count] = %d", [colors count]);
            NSLog(@"currentColorIndex = %d", currentColorIndex);
            
            // Go back to the first color
            currentColorIndex = 0;
        }
        
        // Change color
        [[colors objectAtIndex:currentColorIndex] setStroke];
        
        // Draw the circle
        CGContextAddArc(context, center.x, center.y,
                        currentRadius, 0.0, M_PI * 2.0, YES);
        CGContextStrokePath(context);
    }

...same code as in the book until the end
}

@end

Hope it may helps some of us

Bye


#11

I did a similar thing to irochas, using UIColor objects in an array

I like the elegance of the random solutions in this topic, but there’s something to be said for the control you get from the array method with sequential color use.

[code] // Set the stroke color to light gray
// [[UIColor lightGrayColor] setStroke];

// Make an array of color objects
NSArray *colorArray = [NSArray arrayWithObjects:[UIColor blackColor],[UIColor darkGrayColor],
                       [UIColor lightGrayColor], [UIColor grayColor],[UIColor redColor], 
                       [UIColor greenColor], [UIColor blueColor], [UIColor cyanColor], 
                       [UIColor yellowColor], [UIColor magentaColor], [UIColor orangeColor],
                        [UIColor purpleColor], [UIColor brownColor],nil];

// Draw concentric circles from the outside in
int i = 0;
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
{
    [[colorArray objectAtIndex:i] setStroke];
    ++i;
    if ([colorArray count] == i) i = 0;
    CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
    CGContextStrokePath(context);
}

[/code]


#12

I went with a simpler/easier method to do it in my opinion. Moving the color setting command inside of the for loop and then looking up in the documentation for a way to manually set the color via RGB.

for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
    {
        [[UIColor colorWithRed:((float)rand() / RAND_MAX) green:((float)rand() / RAND_MAX) blue:((float)rand() / RAND_MAX) alpha:1.0] setStroke];
        CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
        CGContextStrokePath(context);
    }

I had to typecast rand() as a float to make sure that I would get a number between 0.0 and 1.0 - I set my alpha locked to 1.0 to make sure the colors weren’t dimmed. Produced a pretty cool image!


#13

I’ve tried using the random generators (like random()) but every time I load up the application the colors of the rings are the same each time so no randomness here.

Why is that?


#14

I made a gradation in a simple way.
It makes use of the value of currentRadius, which changes incrementally from 16.888184 to 576.888184.

    // Draw concentric circles from the outside in
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {

        [[UIColor colorWithWhite:(1 - (currentRadius / 1000)) alpha:1.0] setStroke];
        
        CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI * 2.0, YES);
        CGContextStrokePath(context);
    }

#15

Here’s my take. I create an NSArray and initialized it with the colors of the rainbow. Then I used modulo within the for loop to call set stroke, cycling through my array

    // Create rainbows and shit
    NSArray *rainbowColors = [[NSArray alloc] initWithObjects:[UIColor redColor],
                              [UIColor orangeColor],
                              [UIColor yellowColor],
                              [UIColor greenColor],
                              [UIColor blueColor],
                              [UIColor purpleColor],
                              nil];
    
        
    // Draw concentric circle from the outside in
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
    {   
        [[rainbowColors objectAtIndex:(int)currentRadius/20%6] setStroke];
        CGContextAddArc(context,center.x, center.y, currentRadius, 0.0, M_PI *2.0, YES);
        CGContextStrokePath(context);
    }

Thanks for the challenges. It is the part where I usually learn the most


#16

[quote=“bpedit”]
Hue is the “shade” of a color going round a color wheel. This method varies it from 1 to 0, one trip around the wheel, as the circles are drawn from edge to center of the window.
edit: subbed maxRadius for the denominator in the hue calculation[/quote]

Very cool. My solution was nearly identical to yours except I went for random colors in a slightly different way than the other folks here:

[code] srandom(time(NULL)); // Seed the random number generator

// Draw concentric circles from the outside in
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
{
    float hue = random()/(float)RAND_MAX;
    [[UIColor colorWithHue:hue saturation:1.0 brightness:1.0 alpha:1.0] setStroke];
    CGContextAddArc(context, center.x, center.y, currentRadius, 0.0, M_PI*2.0, YES);
    CGContextStrokePath(context);
}

[/code]


#17

I have tried to create a very simple solution (as I am too stupid to do the clever stuff you have all done!)

In HypnosisView.m
I added an array before drawing the circles…

    // new code 1
    NSArray *colorChoice = [NSArray arrayWithObjects:[UIColor redColor], 
                            [UIColor blueColor], 
                            [UIColor greenColor],
                            [UIColor yellowColor],
                            [UIColor purpleColor], 
                            [UIColor whiteColor],  
                            nil];

Then a little bit of randomisation…

    // draw the circles
    for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20)
    {
        // new code 2
        int colorIndex = rand() % (int)[colorChoice count];
        [[colorChoice objectAtIndex:colorIndex] setStroke];
        
        // draw 
        CGContextAddArc(context, 
                        center.x, 
                        center.y, 
                        currentRadius, 
                        0.0, 
                        M_PI * 2.0, 
                        YES);
        CGContextStrokePath(context);
    }

...etc...

I tried to release this variable at the end of drawRect, but the app hung. I am guessing that as the variable is only declared within the method it will be part of the delegation so will be deallocaed once the app is closed?

Man I am having to work through this!