Monday, July 19, 2010

Wall Paper Editor: Colored RGB Sliders

I have a first cut of a pretty nice RGB slider.  This will work similar to how the Apple slider works in interface builder for RGB.  The color sliders indicate what you will get when you change the slider.

This is a first cut, it uses a .nib file, which seems to sometimes be pesky when using a library.  I would like to redo it to just do it with code.
Here is the sample .nib file.  I have a background label, and an image with an invisible label over it.  I'm going to change the background color of the label as I move the text.  The background label is just so you can see the transparency.

I have 4 sliders for red, green, blue and Transparency.  I also have a text field to display the normalized value of the field (0-255).   As each slider is moved I will change the label image, and recalculate the images for the  sliders.



The code is still beta.  It works for the principle but needs some cleaning up.  Right now it has the following problems.

  1. When the view first comes up it doesn't display the colors.
  2. You can't type a number into the text field.
  3. I really want to support reorientation of the field, and larger sliders.
 

Above is the form running in three different selections.  I display the current color up above, and for each slider I display a gradient showing what happens when you move that slider to the given location.  This allows for a quick visual indicator of how to traverse the RGB color space.

The key to this is the use of UISlider setMinimumTrackImage and setMaximumTrackImage.  Both of these routines take an image and use it.  So whenever the slider is moved I calculate a gradient for all 4 sliders of what moving that slider will do.  I then set the min and max images for the slider.  On my iPhone 3G this runs pretty nicely, although I personally have trouble distinguishing all the colors in the gradient.  The screenshot looks better actually :)


The key is that whenever a slider value is changed I go through and recalculate all 4 sliders.  For each slider I assume all of the other color coordinates are going to remain constant, however that particular slider will vary from 0 to 1 (which is how CoreGraphics handles color values).

I use the slider's position to determine where my two images start and begin, as well as what the value is at that point (which since the sliders go from 0 to 1 just like colors is pretty easy).

My routine to calculate a slider takes the index of the slider and uses it.


- (void) setSlider:(UISlider*) slide colorIndex:(int) colorIndex


I have two arrays of floats for colors.  lcolors and rcolors for the gradient on the left and right of the slider.
Each array has 8 elements for Red, Green, Blue and Alpha for the start and end.

float lcolors[8];
float rcolors[8];


First I set all values equal to the current color.  Only one color is varying.  I could put a continue statement for the color I'm working with, but I just override it a little later.

for (int i=0;i<4;i++)
{
lcolors[i]=colors[i];
lcolors[i+4]=colors[i];
rcolors[i]=colors[i];
rcolors[i+4]=colors[i];
}


Next I set the start and end colors for current color.  I'm going to vary the left gradient from 0 to the currentValue, and the right gradient from the current value to 1.


float currentColorValue=colors[colorIndex];
lcolors[colorIndex]=0;
lcolors[colorIndex+4]=currentColorValue;
rcolors[colorIndex]=currentColorValue;
rcolors[colorIndex+4]=1.0;

Now I calculate the left and right gradient and build an image with them.  The size of the image is the portion of the slider to the left/right of the current value.  (Note that I don't need a image if I'm at the left or right end).

float middlePoint=slide.bounds.size.width*currentColorValue;
CGRect leftSize=CGRectMake(0, 0, middlePoint, slide.bounds.size.height);
CGRect rightSize=CGRectMake(0, 0, slide.bounds.size.width-middlePoint, slide.bounds.size.height);
if (leftSize.size.width>1)
{
UIImage *lImage=[self createImageWithGradient:lgradient withSize:leftSize];
[slide  setMinimumTrackImage:lImage forState:0];
}
if (rightSize.size.width>1)
{
UIImage *rImage=[self createImageWithGradient:rgradient withSize:rightSize];
[slide setMaximumTrackImage:rImage forState:0 ];
}


Where createImageWithGradient is:
- (UIImage*) createImageWithGradient :(CGGradientRef) gradient withSize: (CGRect)size
{
UIGraphicsBeginImageContext(size.size);
CGPoint start=CGPointMake(size.origin.x,size.origin.y+size.size.height*0.25);
CGPoint end=CGPointMake(size.origin.x+size.size.width,size.origin.y+size.size.height*0.25);
CGContextDrawLinearGradient(UIGraphicsGetCurrentContext(), gradient, start, end, 0);
UIImage *viewImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [viewImage stretchableImageWithLeftCapWidth:1 topCapHeight:1];
}




Finally release the gradients:

CGGradientRelease(lgradient);
CGGradientRelease(rgradient);

Everytime I move the slider, I calculate all the colors and fill in the text boxes:
- (void) calcColors
{
[self setSlider:redSlider colorIndex:0];
[self setSlider:greenSlider colorIndex:1];
[self setSlider:blueSlider colorIndex:2];
[self setSlider:transSlider colorIndex:3];
UIColor *currentColor=[UIColor colorWithRed:colors[0] green:colors[1] blue:colors[2] alpha:colors[3]];
self.refLabel.backgroundColor=currentColor;
self.redTextField.text=[NSString stringWithFormat:@"%d",(int)  (colors[0]*255)];
self.greenTextField.text=[NSString stringWithFormat:@"%d",(int) ( colors[1]*255)];
self.blueTextField.text=[NSString stringWithFormat:@"%d",(int)  (colors[2]*255)];
self.transTextField.text=[NSString stringWithFormat:@"%d",(int) ( colors[3]*255)];

}


To make my actions easier, I set the tags of the sliders and text fields equal to the color index associated with them.

For future use I want to migrate this from a view with a nib to a control that doesn't have a nib. It would make it easier to use in multiple projects.  I also plan to expand it so that I can support selecting from a list of colors, and eventually from a  spectrum map.


Below is the entire class, feel free to use it for your own projects.  I would appreciate a attribution (Creative Commons attribution license).


The header
#import
#import


@interface ColorEditor : UIViewController {
IBOutlet UISlider *redSlider;
IBOutlet UISlider *greenSlider;
IBOutlet UISlider *blueSlider;
IBOutlet UISlider *transSlider;
IBOutlet UILabel *refLabel;
IBOutlet UITextField *redTextField;
IBOutlet UITextField *greenTextField;
IBOutlet UITextField *transTextField;
float  colors[4];
CGColorSpaceRef currentColorSpace;
}

- (id) initWithColor:(UIColor *)color;

@property (nonatomic,retain) UISlider *redSlider;
@property (nonatomic,retain) UISlider *greenSlider;
@property (nonatomic,retain) UISlider *blueSlider;
@property (nonatomic,retain) UISlider *transSlider;
@property (nonatomic) CGColorSpaceRef currentColorSpace;
@property (nonatomic,retain) UILabel *refLabel;
@property (nonatomic,retain) UITextField *redTextField;
@property (nonatomic,retain) UITextField *greenTextField;
@property (nonatomic,retain) UITextField *blueTextField;
@property (nonatomic,retain) UITextField *transTextField;


- (IBAction) bob:(id) sender;
- (IBAction) sliderChanged:(id) sender;

- (UIImage*) createImageWithGradient :(CGGradientRef) gradient withSize: (CGRect)size;

- (void) setSlider:(UISlider*) slide colorIndex:(int) colorIndex;
- (void) calcColors;

@end



The source






#import "ColorEditor.h"
#import



@implementation ColorEditor

@synthesize redSlider;
@synthesize greenSlider;
@synthesize blueSlider;
@synthesize transSlider;
@synthesize currentColorSpace;
@synthesize refLabel;

@synthesize redTextField;
@synthesize greenTextField;
@synthesize blueTextField;
@synthesize transTextField;

/*
 // The designated initializer.  Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
        // Custom initialization
    }
    return self;
}
*/

/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
}
*/

/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


- (void)dealloc {
    [super dealloc];
CGColorSpaceRelease(currentColorSpace);
}



- (void) setSlider:(UISlider*) slide colorIndex:(int) colorIndex
{
float currentColorValue=colors[colorIndex];
float lcolors[8];
float rcolors[8];

for (int i=0;i<4;i++)
{
lcolors[i]=colors[i];
lcolors[i+4]=colors[i];
rcolors[i]=colors[i];
rcolors[i+4]=colors[i];
}
lcolors[colorIndex]=0;
lcolors[colorIndex+4]=currentColorValue;
rcolors[colorIndex]=currentColorValue;
rcolors[colorIndex+4]=1.0;
// Now we have two gradients
CGGradientRef lgradient = CGGradientCreateWithColorComponents(currentColorSpace, lcolors, NULL, sizeof(lcolors)/(sizeof(lcolors[0])*4));
CGGradientRef rgradient = CGGradientCreateWithColorComponents(currentColorSpace, rcolors, NULL, sizeof(rcolors)/(sizeof(rcolors[0])*4));
float middlePoint=slide.bounds.size.width*currentColorValue;
CGRect leftSize=CGRectMake(0, 0, middlePoint, slide.bounds.size.height);
CGRect rightSize=CGRectMake(0, 0, slide.bounds.size.width-middlePoint, slide.bounds.size.height);
if (leftSize.size.width>1)
{
UIImage *lImage=[self createImageWithGradient:lgradient withSize:leftSize];
[slide  setMinimumTrackImage:lImage forState:0];
}
if (rightSize.size.width>1)
{
UIImage *rImage=[self createImageWithGradient:rgradient withSize:rightSize];
[slide setMaximumTrackImage:rImage forState:0 ];
}




CGGradientRelease(lgradient);
CGGradientRelease(rgradient);

}
- (UIImage*) createImageWithGradient :(CGGradientRef) gradient withSize: (CGRect)size
{
UIGraphicsBeginImageContext(size.size);
CGPoint start=CGPointMake(size.origin.x,size.origin.y+size.size.height*0.25);
CGPoint end=CGPointMake(size.origin.x+size.size.width,size.origin.y+size.size.height*0.25);
CGContextDrawLinearGradient(UIGraphicsGetCurrentContext(), gradient, start, end, 0);
UIImage *viewImage=UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [viewImage stretchableImageWithLeftCapWidth:1 topCapHeight:1];
}

- (id) initWithColor:(UIColor *)color
{

currentColorSpace = CGColorSpaceCreateDeviceRGB();
const CGFloat *components = CGColorGetComponents(color.CGColor);
for (int i=0;i<4;i++)
{
colors[i]=components[i];
}
//[self calcColors];
return self;
}

- (void) calcColors
{
[self setSlider:redSlider colorIndex:0];
[self setSlider:greenSlider colorIndex:1];
[self setSlider:blueSlider colorIndex:2];
[self setSlider:transSlider colorIndex:3];
UIColor *currentColor=[UIColor colorWithRed:colors[0] green:colors[1] blue:colors[2] alpha:colors[3]];
self.refLabel.backgroundColor=currentColor;
self.redTextField.text=[NSString stringWithFormat:@"%d",(int)  (colors[0]*255)];
self.greenTextField.text=[NSString stringWithFormat:@"%d",(int) ( colors[1]*255)];
self.blueTextField.text=[NSString stringWithFormat:@"%d",(int)  (colors[2]*255)];
self.transTextField.text=[NSString stringWithFormat:@"%d",(int) ( colors[3]*255)];

}

- (IBAction) sliderChanged:(id) sender
{
UISlider *slider=(UISlider*) sender;
int colorIndex=slider.tag;
colors[colorIndex]=slider.value;
[self calcColors];
}
- (IBAction) bob:(id)sender
{
}

@end

No comments:

Post a Comment