How to flip curve-fitted text using Core Graphics? - ios

My app draws a bunch of circles and then draws text within them following the curves as shown below.
The problem as you can see is that some of the text is upsidedown. Its easy enough for me to detect cases where this will happen, so I set about trying to invert that text. The result however is as follows...
As you can see, the letter spacing has been totally jumbled.
I've gotten stuck at this point, I can't see how its losing the spacing so badly & trial and error has failed to find a fix. Any help would be much appreciated. A snippet of the relevant part of my code is below.
#define DEGREES_TO_RADIANS(degrees) ((M_PI * degrees)/ 180)
- (void)drawCurvedLabelForText:(NSString *)titleText withAngle:(float)angle centre:(CGPoint)centre andRadius:(float)textRadius
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIFont *font = [UIFont systemFontOfSize:16.0f];
char *fontName = (char *)[font.fontName cStringUsingEncoding:NSASCIIStringEncoding];
CGContextSelectFont(context, fontName, 12, kCGEncodingMacRoman);
float perimeter = 2 * M_PI * textRadius;
// detect if text is upside-down and needs rotating
BOOL invert = (angle > DEGREES_TO_RADIANS(180.0f);
if (invert)
{
titleText = [self reverseString:titleText];
}
// draw each letter in turn, with correct rotation for each
for (NSUInteger index=0; index<[titleText length]; index++)
{
CGContextSaveGState(context);
NSRange range = {index, 1};
NSString *letter = [titleText substringWithRange:range];
char *c = (char *)[letter cStringUsingEncoding:NSASCIIStringEncoding];
CGSize charSize = [letter sizeWithFont:font];
float x = centre.x + textRadius * cos(angle);
float y = centre.y + textRadius * sin(angle);
float rotateAngle = 0.5 * M_PI;
if (invert)
{
// flip the letter 180 degrees the opposite direction so its not upsidedown
rotateAngle = -0.5 * M_PI;
}
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformMake(1.0,0.0,0.0,-1.0,0.0,0.0));
// draw the text
CGContextTranslateCTM(context, x, y);
CGContextRotateCTM(context, (angle - rotateAngle));
CGContextShowTextAtPoint(context, 0, 0, c, strlen(c));
CGContextRestoreGState(context);
float letterAngle = (charSize.width / perimeter * -2 * M_PI);
angle += letterAngle;
}
}
- (NSString *)reverseString:(NSString *)source
{
NSMutableString *target = [NSMutableString string];
NSUInteger index = [source length];
while (index > 0)
{
index--;
NSRange subStringRange = NSMakeRange(index, 1);
[target appendString:[source substringWithRange:subStringRange]];
}
return target;
}
Note that I've typed it in by hand, making alterations along the way so its more standalone than my actual code which uses class properties, so its possible there's mistakes in the above but the logic should be clear.
My assumption is that "letterAngle", which uses the width of the last drawn letter to determine how much to add onto the "angle" variable for the placement of the next letter, is somehow wrong when the letters are being rotated 180 degrees. Either that or the rotation is shifting it to a different location & I need to offset it somehow with the x/y coordinates. I haven't found answers with either of these though & its possible it could be something else.

Here's a work-around answer, but not really a satisfactory one - use the only monospaced font available on iOS which is Courier. This rotates the text and keeps the spacing the same.
With all other fonts the issue remains unsolved as yet.

Related

Core Graphics Drawing, Bad Performance Degredation

I am using the following core graphics code to draw a waveform of audio data as I am recording. I apologize for tons of code, but here it is (I found it here):
//
// WaveformView.m
//
// Created by Edward Majcher on 7/17/14.
//
#import "WaveformView.h"
//Gain applied to incoming samples
static CGFloat kGain = 10.;
//Number of samples displayed
static int kMaxWaveforms = 80.;
#interface WaveformView ()
#property (nonatomic) BOOL addToBuffer;
//Holds kMaxWaveforms number of incoming samples,
//80 is based on half the width of iPhone, adding a 1 pixel line between samples
#property (strong, nonatomic) NSMutableArray* bufferArray;
+ (float)RMS:(float *)buffer length:(int)bufferSize;
#end
#implementation WaveformView
- (void)awakeFromNib
{
[super awakeFromNib];
self.bufferArray = [NSMutableArray array];
}
-(void)updateBuffer:(float *)buffer withBufferSize:(UInt32)bufferSize
{
if (!self.addToBuffer) {
self.addToBuffer = YES;
return;
} else {
self.addToBuffer = NO;
}
float rms = [WaveformView RMS:buffer length:bufferSize];
if ([self.bufferArray count] == kMaxWaveforms) {
//##################################################
// [self.bufferArray removeObjectAtIndex:0];
}
[self.bufferArray addObject:#(rms * kGain)];
[self setNeedsDisplay];
}
+ (float)RMS:(float *)buffer length:(int)bufferSize {
float sum = 0.0;
for(int i = 0; i < bufferSize; i++) {
sum += buffer[i] * buffer[i];
}
return sqrtf( sum / bufferSize );
}
// *****************************************************
- (void)drawRect:(CGRect)rect
{
CGFloat midX = CGRectGetMidX(rect);
CGFloat maxX = CGRectGetMaxX(rect);
CGFloat midY = CGRectGetMidY(rect);
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw out center line
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 1.);
CGContextMoveToPoint(context, 0., midY);
CGContextAddLineToPoint(context, maxX, midY);
CGContextStrokePath(context);
CGFloat x = 0.;
for (NSNumber* n in self.bufferArray) {
CGFloat height = 20 * [n floatValue];
CGContextMoveToPoint(context, x, midY - height);
CGContextAddLineToPoint(context, x, midY + height);
CGContextStrokePath(context);
x += 2;
}
if ([self.bufferArray count] >= kMaxWaveforms) {
[self addMarkerInContext:context forX:midX forRect:rect];
} else {
[self addMarkerInContext:context forX:x forRect:rect];
}
}
- (void)addMarkerInContext:(CGContextRef)context forX:(CGFloat)x forRect:(CGRect)rect
{
CGFloat maxY = CGRectGetMaxY(rect);
CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor greenColor].CGColor);
CGContextFillEllipseInRect(context, CGRectMake(x - 1.5, 0, 3, 3));
CGContextMoveToPoint(context, x, 0 + 3);
CGContextAddLineToPoint(context, x, maxY - 3);
CGContextStrokePath(context);
CGContextFillEllipseInRect(context, CGRectMake(x - 1.5, maxY - 3, 3, 3));
}
#end
So as I am recording audio, the waveform drawing gets more and more jittery, kind of like a game that has bad frame rates. I tried contacting the owner of this piece of code, but no luck. I have never used core graphics, so I'm trying to figure out why performance is so bad. Performance starts to degrade at around 2-3 seconds worth of audio (the waveform doesn't even fill the screen).
My first question is, is this redrawing the entire audio history every time drawRect is called? If you look in the drawRect function (marked by asterisks), there is a variable called CGRect x. This seems to affect the position at which the waveform is being drawn (if you set it to 60 instead of 0, it starts at x=60 pixels instead of x=0 pixels).
From my viewController, I pass in the audioData which gets stored in the self.bufferArray property. So when that loop goes through to draw the data, it seems like it's starting at zero and working its way up every time drawRect is getting called, which means that for every new piece of audio data added, drawRect gets called, and it redraws the entire waveform plus the new piece of audio data.
If that is the problem, does anyone know how I can optimize this piece of code? I tried emptying the bufferArray after the loop so that it contained only new data, but that didn't work.
If this is not the problem, are there any core graphics experts that can figure out what the problem is?
I should also mention that I commented out a piece of code (marked with ### at signs) because I need the entire waveform. I don't want it to remove pieces of the waveform at the beginning. The iOS Voice Memos app can hold a waveform of audio without performance degradation.

How to make equal space between texts in the circular menu?

I am creating a circular slider menu, I am using coregraphics to draw the control UI, Here I am trying to draw words on the circumference of a circle. But i cant able to make the spaces equally.
Here is my code and output. Please help me to give equal spaces between each word.
In drawRect
float angleStep = 2 * M_PI / [menuArray count];
float angle = degreesToRadians(90);
textRadius = textRadius - 12;
for (NSString* text in`enter code here` titleArray)
{
[self drawStringAtContext:ctx string:text atAngle:angle withRadius:textRadius];
angle -= angleStep;
}
//////////
- (void) drawStringAtContext:(CGContextRef) context string:(NSString*) text atAngle:(float)angle withRadius:(float) radis
{
CGSize textSize = [text sizeWithFont:[UIFont systemFontOfSize:11.0]];
float perimeter = 2 * M_PI * radis;
float textAngle = textSize.width / perimeter * 2 * M_PI;
angle += textAngle / 2;
for (int index = 0; index < [text length]; index++)
{
NSRange range = {index, 1};
NSString* letter = [text substringWithRange:range];
char* c = (char*)[letter cStringUsingEncoding:NSASCIIStringEncoding];
CGSize charSize = [letter sizeWithFont:[UIFont systemFontOfSize:11.0]];
float x = radis * cos(angle);
float y = radis * sin(angle);
float letterAngle = (charSize.width / perimeter * -2 * M_PI);
CGContextSaveGState(context);
CGContextTranslateCTM(context, x, y);
CGContextRotateCTM(context, (angle - 0.5 * M_PI));
CGContextShowTextAtPoint(context, 0, 0, c, strlen(c));
CGContextRestoreGState(context);
angle += letterAngle;
}
}
Output

curved text starting point

I'm using this to generate a curved text:
- (UIImage*)createCircularText:(NSString*)text withSize:(CGSize)size andCenter:(CGPoint)center
{
UIFont *font = [UIFont fontWithName:#"HelveticaNeue-Light" size:15];
// Start drawing
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Retrieve the center and set a radius
CGFloat r = center.x / 3;
// Start by adjusting the context origin
// This affects all subsequent operations
CGContextTranslateCTM(context, center.x, center.y);
// Calculate the full extent
CGFloat fullSize = 0;
for (int i = 0; i < [text length]; i++)
{
NSString *letter = [text substringWithRange:NSMakeRange(i, 1)];
CGSize letterSize = [letter sizeWithAttributes:#{NSFontAttributeName:font}];
fullSize += letterSize.width;
}
// Initialize the consumed space
CGFloat consumedSize = 0.0f;
// Iterate through the alphabet
for (int i = 0; i < [text length]; i++)
{
// Retrieve the letter and measure its display size
NSString *letter = [text substringWithRange:NSMakeRange(i, 1)];
CGSize letterSize = [letter sizeWithAttributes:#{NSFontAttributeName:font}];
// Calculate the current angular offset
//CGFloat theta = i * (2 * M_PI / ((float)[text length] * 3));
// Move the pointer forward, calculating the new percentage of travel along the path
consumedSize += letterSize.width / 2.0f;
CGFloat percent = consumedSize / fullSize;
CGFloat theta = (percent * 2 * M_PI) / ((float)[text length] / 4);
consumedSize += letterSize.width / 2.0f;
// Encapsulate each stage of the drawing
CGContextSaveGState(context);
// Rotate the context
CGContextRotateCTM(context, theta);
// Translate up to the edge of the radius and move left by
// half the letter width. The height translation is negative
// as this drawing sequence uses the UIKit coordinate system.
// Transformations that move up go to lower y values.
CGContextTranslateCTM(context, -letterSize.width / 2, -r);
// Draw the letter and pop the transform state
[letter drawAtPoint:CGPointMake(0, 0) withAttributes:#{NSFontAttributeName:font}];
CGContextRestoreGState(context);
}
// Retrieve and return the image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
and i get this back:
The problem is that the text starts at 0° but I actually want it to begin more at the left, so that the center of the string is at 0°. How to accomplish this?
Two options should work:
After drawing all of the text, rotate the context half of the use angle and get the image from the context at that point.
Make two passes. The first simply calculates the required angle to draw the text. The second pass does what you do now. Except in the 2nd pass, subtract half of the required total angle from each letter's angle.

Dynamic CCLabelTTF font size in a specified rectangle - cocos2d

I am trying to create a text box that is specified by x, y, w, and h that can accept any string and adjust the font size to be as big as possible and still fit in the box. I have been trying several approaches, but it doesn't quite work. Here is my closest attempt:
+ (CCLabelTTF*) wrap_text :(id)not_self :(NSString *)label :(double)x :(double)y : (double)w :(double)h :(float) fontScale :(ccColor3B) color_front
{
CGSize size;
size = [[CCDirector sharedDirector] winSize];
int boxWidth = (w - x) * size.width;
int boxHeight = (h - y) * size.height;
double middleX = ((w-x)/2 + x);
double middleY = (1 - y);
middleX *= size.width;
middleY *= size.height;
//float font_size = size.height * h * fontScale;
int font_min = 5;
int font_max = 300;
int font_size = 5;
int font_size_final = -1;
int max_interations = 10;
int i;
NSString *hello = label;
UIFont *font = [UIFont fontWithName:GLOBAL_FONT size:font_size];
CGSize realSize = [hello sizeWithFont:font constrainedToSize:CGSizeMake(boxWidth, boxHeight) lineBreakMode:UILineBreakModeWordWrap ];
i = 0;
while ( true ) {
if(realSize.width < boxWidth && realSize.height < boxHeight){
//we found a good value, let's record it and try to go bigger
font_size_final = font_size;
font_min = font_size;
font_size = (font_max - font_min) / 2 + font_min;
}else if(realSize.width > boxWidth || realSize.height > boxHeight){
//too big, let's try a smaller font
font_max = font_size;
font_size = (font_max - font_min) / 2 + font_min;
}
if(font_max == font_min || i >= max_interations)
break;
font = [UIFont fontWithName:GLOBAL_FONT size:font_size];
realSize = [hello sizeWithFont:font constrainedToSize:CGSizeMake(boxWidth, boxHeight) lineBreakMode:UILineBreakModeWordWrap ];
i++;
}
G_label = [CCLabelTTF labelWithString:label fontName:GLOBAL_FONT fontSize:font_size dimensions:CGSizeMake(boxWidth, boxHeight) hAlignment:UITextAlignmentCenter];
G_label.color = color_front;
G_label.anchorPoint = ccp(0.5f, 1);
G_label.position = ccp( middleX , middleY );
[not_self addChild:G_label];
return G_label;
}
If I change boxWidth and boxHeight to 200, I get slightly better results, but I don't understand why and it's still not perfect. Can somebody tell me where I am going astray? Thanks.
*This answer is not mine: original answer is posted here - https://stackoverflow.com/a/9060833/1590951
above post gives credit to http://www.11pixel.com/blog/28/resize-multi-line-text-to-fit-uilabel-on-iphone/
I have only made minor edits it to make it easier to test in cocos2d (tested on ancient cc2d 1.1 so if you have a different version you'll have to make the relevant edits)
You could try the following as a test to see if this will work for your needs:
NSString *fontString = #"Arial";
CGSize targetSize = CGSizeMake(300.0f, 300.0f);
int i;
int fontSizeStep = 1;
int fontSizeMin = 6;
int fontSizeMax = 28;
CCSprite *bg = [CCSprite spriteWithFile:#"Default.png"];
[bg setScaleX: targetSize.width / bg.contentSize.width];
[bg setScaleY: targetSize.height / bg.contentSize.height];
[bg setPosition:ccp(s.width / 2.0f, s.height / 2.0f)];
[bg setColor:ccc3(128, 128, 128)];
[self addChild:bg];
NSString *sampleText = #"Now the way that the book winds up is this: Tom and me found the money that the robbers hid in the cave, and it made us rich. We got six thousand dollars apiece -­ all gold. It was an awful sight of money when it was piled up. Well, Judge Thatcher he took it and put it out at interest, and it fetched us a dollar a day apiece all the year round -­ more than a body could tell what to do with. The Widow Douglas she took me for her son, and allowed she would sivilize me; but it was rough living in the house all the time, considering how dismal regular and decent the widow was in all her ways; and so when I couldn't stand it no longer I lit out. I got into my old rags and my sugar-hogshead again, and was free and satisfied. But Tom Sawyer he hunted me up and said he was going to start a band of robbers, and I might join if I would go back to the widow and be respectable. So I went back.";
// NSString *sampleText = #"Once upon a midnight dreary, while I pondered, weak and weary, Over many a quaint and curious volume of forgotten lore— While I nodded, nearly napping, suddenly there came a tapping, As of some one gently rapping, rapping at my chamber door— \"'Tis some visitor,\" I muttered, \"tapping at my chamber door— Only this and nothing more.";
UIFont *font = [UIFont fontWithName:fontString size:fontSizeMax];
/* Time to calculate the needed font size.
This for loop starts at the largest font size, and decreases by two point sizes (i=i-2)
Until it either hits a size that will fit or hits the minimum size we want to allow (i > 10) */
for(i = fontSizeMax; i > fontSizeMin; i-=fontSizeStep)
{
// Set the new font size.
font = [font fontWithSize:i];
// You can log the size you're trying: NSLog(#"Trying size: %u", i);
/* This step is important: We make a constraint box
using only the fixed WIDTH of the UILabel. The height will
be checked later. */
CGSize constraintSize = CGSizeMake(targetSize.width, MAXFLOAT);
// This step checks how tall the label would be with the desired font.
CGSize labelSize = [sampleText sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:CCLineBreakModeWordWrap];
/* Here is where you use the height requirement!
Set the value in the if statement to the height of your UILabel
If the label fits into your required height, it will break the loop
and use that font size. */
if(labelSize.height <= targetSize.height)
{
NSLog(#"Break on: %u", i);
break;
}
}
if(i == fontSizeMin)
{
NSLog(#"* possibly truncated text *");
}
// You can see what size the function is using by outputting: NSLog(#"Best size is: %u", i);
CCLabelTTF *label = [CCLabelTTF labelWithString:sampleText dimensions:targetSize alignment:CCTextAlignmentLeft fontName:fontString fontSize:i];
[label setPosition:ccp(s.width / 2.0f, s.height / 2.0f)];
[self addChild:label];

Determine primary and secondary colors of a UIImage

I was wondering if someone could help me figure out how I can determine the main primary and secondary colors in a UIImage. I haven't been able to find anything terribly useful in Google.
Based on Panic blog above, here's a fast method (<10ms depending on variables).
Takes a UIImage input and outputs:
background colour
primary colour
secondary colour
When passing the UIImage to the method, pass an 'edge' as well. This refers to which part of the image is exposed to the rest of the view (see image below for clarification):
edge = 0 = top
edge = 1 = left
edge = 2 = bottom
edge = 3 = right
**
Step 1.
** set dimension. This refers to how many pixels across and down the image should be.
Step 2. resize the image based on the dimension input (e.g. 20 x 20 = 400px).
Step 3. create a colour array, pulling the RGB values from the raw data created in step 2. Additionally collect the pixels that run along the chosen edge.
Step 4. calculate the edge or background colour. This is used later for contrasting the accent colours, as per the Panic blog.
Step 5. go through the collected colours (RGB values) and determine whether or not they contrast sufficiently with the edge/background colour. If they do not, the minimum required contrast is reduced. Also, for each colour object set its 'distance' to other colours. This is a rough estimate of how similar the colour is to other colours in the image.
Step 6. sort the accents by distance (shortest distance means most similar), with the most similar colour appearing at the top. Set the most frequent colour as the primary colour.
Step 7. go through the remaining colours and determine which contrasts the most with the primary colour. Then set your secondary colour.
Caveat: I'm sure the method could be improved, but it's a quick starting point. You could for instance determine the colour of the pixels in the top right or top left of the image (where back icons etc are often placed) and suggest a contrasting colour for the icons there.
Here's the code:
-(NSDictionary *)coloursForImage:(UIImage *)image forEdge:(int)edge {
NSLog(#"start");
//1. set vars
float dimension = 20;
//2. resize image and grab raw data
//this part pulls the raw data from the image
CGImageRef imageRef = [image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char *rawData = (unsigned char*) calloc(dimension * dimension * 4, sizeof(unsigned char));
NSUInteger bytesPerPixel = 4;
NSUInteger bytesPerRow = bytesPerPixel * dimension;
NSUInteger bitsPerComponent = 8;
CGContextRef context = CGBitmapContextCreate(rawData, dimension, dimension, bitsPerComponent, bytesPerRow, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGColorSpaceRelease(colorSpace);
CGContextDrawImage(context, CGRectMake(0, 0, dimension, dimension), imageRef);
CGContextRelease(context);
//3. create colour array
NSMutableArray * colours = [NSMutableArray new];
float x = 0, y = 0; //used to set coordinates
float eR = 0, eB = 0, eG = 0; //used for mean edge colour
for (int n = 0; n<(dimension*dimension); n++){
Colour * c = [Colour new]; //create colour
int i = (bytesPerRow * y) + x * bytesPerPixel; //pull index
c.r = rawData[i]; //set red
c.g = rawData[i + 1]; //set green
c.b = rawData[i + 2]; //set blue
[colours addObject:c]; //add colour
//add to edge if true
if ((edge == 0 && y == 0) || //top
(edge == 1 && x == 0) || //left
(edge == 2 && y == dimension-1) || //bottom
(edge == 3 && x == dimension-1)){ //right
eR+=c.r; eG+=c.g; eB+=c.b; //add the colours
}
//update pixel coordinate
x = (x == dimension - 1) ? 0 : x+1;
y = (x == 0) ? y+1 : y;
}
free(rawData);
//4. calculate edge colour
Colour * e = [Colour new];
e.r = eR/dimension;
e.g = eG/dimension;
e.b = eB/dimension;
//5. calculate the frequency of colour
NSMutableArray * accents = [NSMutableArray new]; //holds valid accents
float minContrast = 3.1; //play with this value
while (accents.count < 3) { //minimum number of accents
for (Colour * a in colours){
//NSLog(#"contrast value is %f", [self contrastValueFor:a andB:e]);
//5.1 ignore if it does not contrast with edge
if ([self contrastValueFor:a andB:e] < minContrast){ continue;}
//5.2 set distance (frequency)
for (Colour * b in colours){
a.d += [self colourDistance:a andB:b];
}
//5.3 add colour to accents
[accents addObject:a];
}
minContrast-=0.1f;
}
//6. sort colours by the most common
NSArray * sorted = [[NSArray arrayWithArray:accents] sortedArrayUsingDescriptors:#[[[NSSortDescriptor alloc] initWithKey:#"d" ascending:true]]];
//6.1 set primary colour (most common)
Colour * p = sorted[0];
//7. get most contrasting colour
float high = 0.0f; //the high
int index = 0; //the index
for (int n = 1; n < sorted.count; n++){
Colour * c = sorted[n];
float contrast = [self contrastValueFor:c andB:p];
//float sat = [self saturationValueFor:c andB:p];
if (contrast > high){
high = contrast;
index = n;
}
}
//7.1 set secondary colour (most contrasting)
Colour * s = sorted[index];
NSLog(#"er %i eg %i eb %i", e.r, e.g, e.b);
NSLog(#"pr %i pg %i pb %i", p.r, p.g, p.b);
NSLog(#"sr %i sg %i sb %i", s.r, s.g, s.b);
NSMutableDictionary * result = [NSMutableDictionary new];
[result setValue:[UIColor colorWithRed:e.r/255.0f green:e.g/255.0f blue:e.b/255.0f alpha:1.0f] forKey:#"background"];
[result setValue:[UIColor colorWithRed:p.r/255.0f green:p.g/255.0f blue:p.b/255.0f alpha:1.0f] forKey:#"primary"];
[result setValue:[UIColor colorWithRed:s.r/255.0f green:s.g/255.0f blue:s.b/255.0f alpha:1.0f] forKey:#"secondary"];
NSLog(#"end");
return result;
}
-(float)contrastValueFor:(Colour *)a andB:(Colour *)b {
float aL = 0.2126 * a.r + 0.7152 * a.g + 0.0722 * a.b;
float bL = 0.2126 * b.r + 0.7152 * b.g + 0.0722 * b.b;
return (aL>bL) ? (aL + 0.05) / (bL + 0.05) : (bL + 0.05) / (aL + 0.05);
}
-(float)saturationValueFor:(Colour *)a andB:(Colour *)b {
float min = MIN(a.r, MIN(a.g, a.b)); //grab min
float max = MAX(b.r, MAX(b.g, b.b)); //grab max
return (max - min)/max;
}
-(int)colourDistance:(Colour *)a andB:(Colour *)b {
return abs(a.r-b.r)+abs(a.g-b.g)+abs(a.b-b.b);
}
The Colour object is a simple custom class:
#interface Colour : NSObject
#property int r, g, b, d;
#end
The engineers at Panic wrote an algorithm for doing colour analysis similar to that found in iTunes 11 (to determine good primary, secondary, and detail colours). They posted an explanation of how it works and the code on their blog.

Resources