Skip to content

Commit

Permalink
Updated to support interpolation between two colors. Updated sample a…
Browse files Browse the repository at this point in the history
…pp with slope factor slider and live preview.
  • Loading branch information
jernejstrasner committed Jul 28, 2015
1 parent d78488e commit 0d3df94
Showing 7 changed files with 166 additions and 90 deletions.
6 changes: 3 additions & 3 deletions SmoothGradient.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 47;
objectVersion = 46;
objects = {

/* Begin PBXBuildFile section */
@@ -207,7 +207,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = JST;
LastUpgradeCheck = 0630;
LastUpgradeCheck = 0640;
ORGANIZATIONNAME = "Jernej Strasner";
TargetAttributes = {
B8688510182757B50004B837 = {
@@ -216,7 +216,7 @@
};
};
buildConfigurationList = B86884EB182757B40004B837 /* Build configuration list for PBXProject "SmoothGradient" */;
compatibilityVersion = "Xcode 6.3";
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0630"
LastUpgradeVersion = "0640"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
45 changes: 34 additions & 11 deletions SmoothGradient/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -31,35 +31,58 @@
<view key="view" contentMode="scaleToFill" id="d4d-5p-x2B" customClass="JSTGradientView">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Slope factor: 2x" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="GxW-Ne-JRM">
<rect key="frame" x="8" y="574" width="102" height="18"/>
<color key="backgroundColor" white="1" alpha="0.5" colorSpace="calibratedWhite"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="15"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="GxW-Ne-JRM" firstAttribute="leading" secondItem="d4d-5p-x2B" secondAttribute="leading" constant="8" id="7Uc-2f-JmB"/>
<constraint firstItem="NUo-el-9Sz" firstAttribute="top" secondItem="GxW-Ne-JRM" secondAttribute="bottom" constant="8" id="THP-M3-zYe"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="startColor">
<color key="value" red="1" green="0.0" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="value" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="color" keyPath="endColor">
<color key="value" white="1" alpha="1" colorSpace="calibratedWhite"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="boolean" keyPath="reverse" value="YES"/>
</userDefinedRuntimeAttributes>
</view>
<navigationItem key="navigationItem" id="39M-QE-wSc">
<nil key="title"/>
<segmentedControl key="titleView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="0" id="1W4-Ja-TMO">
<rect key="frame" x="236" y="7" width="128" height="30"/>
<barButtonItem key="leftBarButtonItem" title="Reset" id="crg-g1-Yk8">
<connections>
<action selector="reset:" destination="jAs-ix-hgd" id="DB5-ch-joa"/>
</connections>
</barButtonItem>
<slider key="titleView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="2" minValue="1" maxValue="15" id="9Qc-FJ-33v">
<rect key="frame" x="67" y="7" width="448" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<segments>
<segment title="Normal"/>
<segment title="Smooth"/>
</segments>
<connections>
<action selector="switchMode:" destination="jAs-ix-hgd" eventType="valueChanged" id="1JC-EN-525"/>
<action selector="slopeFactorChanged:" destination="jAs-ix-hgd" eventType="valueChanged" id="aWI-5p-lai"/>
</connections>
</slider>
<barButtonItem key="rightBarButtonItem" title="Reverse" id="88R-Ri-uZ8">
<connections>
<action selector="reverse:" destination="jAs-ix-hgd" id="GXd-dx-DS2"/>
</connections>
</segmentedControl>
</barButtonItem>
</navigationItem>
<connections>
<outlet property="factorLabel" destination="GxW-Ne-JRM" id="M1F-AH-ZY8"/>
<outlet property="gradientView" destination="d4d-5p-x2B" id="1YP-DB-9es"/>
<outlet property="slider" destination="9Qc-FJ-33v" id="pGq-Dj-VQJ"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="g7c-fJ-Nvs" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="163.125" y="107.74647887323943"/>
<point key="canvasLocation" x="163" y="107"/>
</scene>
</scenes>
</document>
3 changes: 2 additions & 1 deletion SmoothGradient/JSTGradientView.h
Original file line number Diff line number Diff line change
@@ -10,8 +10,9 @@

@interface JSTGradientView : UIView

@property (nonatomic, assign) IBInspectable BOOL smooth;
@property (nonatomic, assign) IBInspectable CGFloat slopeFactor;
@property (nonatomic, assign) IBInspectable BOOL reverse;
@property (nonatomic, retain) IBInspectable UIColor *startColor;
@property (nonatomic, retain) IBInspectable UIColor *endColor;

@end
157 changes: 86 additions & 71 deletions SmoothGradient/JSTGradientView.m
Original file line number Diff line number Diff line change
@@ -15,7 +15,11 @@ - (void)setter:(type)value { \
[self setNeedsDisplay]; \
}

@implementation JSTGradientView
@implementation JSTGradientView {
CGColorSpaceRef colorSpace;
CGFloat startColorComps[4];
CGFloat endColorComps[4];
}

- (instancetype)init
{
@@ -41,96 +45,107 @@ - (instancetype)initWithFrame:(CGRect)frame
- (void)setup
{
self.contentMode = UIViewContentModeRedraw;
colorSpace = CGColorSpaceCreateDeviceRGB();
}

- (void)dealloc
{
CGColorSpaceRelease(colorSpace);
}

SyntesizeRedrawableProperty(setSmooth, _smooth, BOOL)
SyntesizeRedrawableProperty(setReverse, _reverse, BOOL)
SyntesizeRedrawableProperty(setStartColor, _startColor, UIColor *)
SyntesizeRedrawableProperty(setSlopeFactor, _slopeFactor, CGFloat)

- (void)setStartColor:(UIColor *)startColor
{
if (_startColor == startColor) return;
_startColor = startColor;

GetColorComponents(startColor.CGColor, startColorComps);
[self setNeedsDisplay];
}

- (void)setEndColor:(UIColor *)endColor
{
if (_endColor == endColor) return;
_endColor = endColor;

GetColorComponents(endColor.CGColor, endColorComps);
[self setNeedsDisplay];
}

void GetColorComponents(CGColorRef color, CGFloat *outComponents) {
size_t numberOfComponents = CGColorGetNumberOfComponents(color);
if (numberOfComponents != 4) assert("Only RGBA colors supported!");
const CGFloat *components = CGColorGetComponents(color);
memcpy(outComponents, components, sizeof(CGFloat)*numberOfComponents);
}

- (void)drawRect:(CGRect)rect
{
// Draw the gradient background
// Prepare general variables
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

// Get color components
CGColorRef color = _startColor.CGColor;
const CGFloat *startColorComponents = CGColorGetComponents(color);
CGFloat *colorComponents = calloc(8, sizeof(CGFloat));
memset(colorComponents, 1, 8*sizeof(CGFloat)); // Set to white by default
memcpy(colorComponents, startColorComponents, CGColorGetNumberOfComponents(color)*sizeof(CGFloat)); // Copy first color
memcpy(colorComponents+4, startColorComponents, CGColorGetNumberOfComponents(color)*sizeof(CGFloat)); // Copy second color
colorComponents[3] = _reverse ? 1.0f : 0.0f; // Alpha value for the start color
colorComponents[7] = _reverse ? 0.0f : 1.0f; // Alpha value for the end color

if (_smooth) {
// Define the shading callbacks
CGFunctionCallbacks callbacks = {0, shadingFunction, NULL};

// As input to our function we want 1 value in the range [0.0, 1.0].
// This is our position within the 'gradient'.
size_t domainDimension = 1;
CGFloat domain[2] = {0.0f, 1.0f};

// The output of our function is 2 values, each in the range [0.0, 1.0].
// This is our selected color for the input position.
// The 2 values are the white and alpha components.
size_t rangeDimension = 4;
CGFloat range[8] = {
0.0f, 1.0f,
0.0f, 1.0f,
0.0f, 1.0f,
0.0f, 1.0f
};

// Create the shading finction
CGFunctionRef function = CGFunctionCreate(colorComponents, domainDimension, domain, rangeDimension, range, &callbacks);

// Create the shading object
CGShadingRef shading = CGShadingCreateAxial(colorSpace, CGPointMake(1, rect.size.height), CGPointMake(1, 0), function, YES, YES);

// Draw the shading
CGContextDrawShading(context, shading);

// Clean up
CGFunctionRelease(function);
CGShadingRelease(shading);
}
else {
// Color locations
CGFloat locations[2] = {0.0f, 1.0f};

// The gradient
CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace, colorComponents, locations, 2);

// Draw the gradient
CGContextDrawLinearGradient(context, gradient, CGPointMake(1, rect.size.height), CGPointMake(1, 0), kCGGradientDrawsAfterEndLocation | kCGGradientDrawsBeforeStartLocation);

// Clean up
CGGradientRelease(gradient);
}

// Shading function info
CGFloat functionInfo[9];
// First color
memcpy(functionInfo, (_reverse ? endColorComps : startColorComps), sizeof(CGFloat)*4);
// Second color
memcpy(functionInfo+4, (_reverse ? startColorComps : endColorComps), sizeof(CGFloat)*4);
// Slope factor
functionInfo[8] = _slopeFactor;

// Define the shading callbacks
CGFunctionCallbacks callbacks = {0, shadingFunction, NULL};

// As input to our function we want 1 value in the range [0.0, 1.0].
// This is our position within the gradient.
size_t domainDimension = 1;
CGFloat domain[2] = {0.0f, 1.0f};

// The output of our shading function are 4 values, each in the range [0.0, 1.0].
// By specifying 4 ranges here, we limit each color component to that range. Values outside of the range get clipped.
size_t rangeDimension = 4;
CGFloat range[8] = {
0.0f, 1.0f, // R
0.0f, 1.0f, // G
0.0f, 1.0f, // B
0.0f, 1.0f // A
};

// Create the shading function
CGFunctionRef function = CGFunctionCreate(functionInfo, domainDimension, domain, rangeDimension, range, &callbacks);

// Create the shading object
CGShadingRef shading = CGShadingCreateAxial(colorSpace, CGPointMake(1, rect.size.height), CGPointMake(1, 0), function, YES, YES);

// Draw the shading
CGContextDrawShading(context, shading);

// Clean up
CGColorSpaceRelease(colorSpace);
free(colorComponents);
CGFunctionRelease(function);
CGShadingRelease(shading);
}

// This is the callback of our shading function.
// info: not needed
// info: color and slope information
// inData: contains a single float that gives is the current position within the gradient
// outData: we fill this with the color to display at the given position
static void shadingFunction(void *info, const CGFloat *inData, CGFloat *outData)
{
CGFloat *color = info; // Pointer to the components of the 2 colors we're interpolating (only using one color for now)
CGFloat *colors = info; // Pointer to the components of the 2 colors we're interpolating
CGFloat slopeFactor = colors[8]; // Slope factor stored in the colors array
float p = inData[0]; // Position in gradient
outData[0] = color[0];
outData[1] = color[1];
outData[2] = color[2];
outData[3] = fabs(color[3]-slope(p, 2.0f)); // Alpha channel interpolation
float q = slope(p, slopeFactor); // Slope value
outData[0] = colors[0] + (colors[4] - colors[0])*q;
outData[1] = colors[1] + (colors[5] - colors[1])*q;
outData[2] = colors[2] + (colors[6] - colors[2])*q;
outData[3] = colors[3] + (colors[7] - colors[3])*q;
}

// Distributes values on a slope aka. ease-in ease-out
static float slope(float x, float A) {
static float slope(float x, float A)
{
float p = powf(x, A);
return p/(p + powf(1.0f-x, A));
}
6 changes: 5 additions & 1 deletion SmoothGradient/JSTViewController.h
Original file line number Diff line number Diff line change
@@ -13,7 +13,11 @@
@interface JSTViewController : UIViewController

@property (weak, nonatomic) IBOutlet JSTGradientView *gradientView;
@property (weak, nonatomic) IBOutlet UISlider *slider;
@property (weak, nonatomic) IBOutlet UILabel *factorLabel;

- (IBAction)switchMode:(UISegmentedControl *)sender;
- (IBAction)reset:(id)sender;
- (IBAction)reverse:(id)sender;
- (IBAction)slopeFactorChanged:(UISlider *)sender;

@end
37 changes: 35 additions & 2 deletions SmoothGradient/JSTViewController.m
Original file line number Diff line number Diff line change
@@ -11,9 +11,42 @@

@implementation JSTViewController

- (IBAction)switchMode:(UISegmentedControl *)sender
- (void)viewDidLoad
{
self.gradientView.smooth = sender.selectedSegmentIndex;
[super viewDidLoad];

[self reset];
}

- (IBAction)reset:(id)sender
{
[self reset];
}

- (IBAction)reverse:(id)sender
{
self.gradientView.reverse = !self.gradientView.reverse;
}

- (IBAction)slopeFactorChanged:(UISlider *)sender
{
float factor = log(sender.value);
printf("Factor: %f\n", factor);
self.gradientView.slopeFactor = factor;
[self updateLabel];
}

- (void)reset
{
self.gradientView.reverse = NO;
self.gradientView.slopeFactor = 2.0f;
self.slider.value = pow(M_E, 2.0f);
[self updateLabel];
}

- (void)updateLabel
{
self.factorLabel.text = [NSString stringWithFormat:@"Slope factor: %0.4fx", self.gradientView.slopeFactor];
}

@end

0 comments on commit 0d3df94

Please sign in to comment.