|
| 1 | +// The MIT License (MIT) |
| 2 | +// |
| 3 | +// Copyright (c) 2014 Jernej Strasner |
| 4 | +// |
| 5 | +// Permission is hereby granted, free of charge, to any person obtaining a copy of |
| 6 | +// this software and associated documentation files (the "Software"), to deal in |
| 7 | +// the Software without restriction, including without limitation the rights to |
| 8 | +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
| 9 | +// the Software, and to permit persons to whom the Software is furnished to do so, |
| 10 | +// subject to the following conditions: |
| 11 | +// |
| 12 | +// The above copyright notice and this permission notice shall be included in all |
| 13 | +// copies or substantial portions of the Software. |
| 14 | +// |
| 15 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 16 | +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
| 17 | +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
| 18 | +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
| 19 | +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| 20 | +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 21 | + |
| 22 | +#import "RCTGradient.h" |
| 23 | + |
| 24 | +#import "RCTLog.h" |
| 25 | +#import "RCTUtils.h" |
| 26 | +#import "UIImageUtils.h" |
| 27 | + |
| 28 | +// Info struct to pass to shading function |
| 29 | +struct _JSTFunctionInfo { |
| 30 | + CGFloat startColor[4]; |
| 31 | + CGFloat endColor[4]; |
| 32 | + CGFloat slopeFactor; |
| 33 | +}; |
| 34 | +typedef struct _JSTFunctionInfo JSTFunctionInfo; |
| 35 | +typedef struct _JSTFunctionInfo* JSTFunctionInfoRef; |
| 36 | + |
| 37 | +static JSTFunctionInfoRef JSTFunctionInfoCreate() { |
| 38 | + return (JSTFunctionInfoRef)malloc(sizeof(JSTFunctionInfo)); |
| 39 | +} |
| 40 | + |
| 41 | +static void JSTFunctionInfoRelease(JSTFunctionInfoRef info) { |
| 42 | + if (info != NULL) { |
| 43 | + free(info); |
| 44 | + } |
| 45 | +} |
| 46 | + |
| 47 | +// Distributes values on a slope aka. ease-in ease-out |
| 48 | +static float JSTSlope(float x, float A) |
| 49 | +{ |
| 50 | + float p = powf(x, A); |
| 51 | + return p/(p + powf(1.0f-x, A)); |
| 52 | +} |
| 53 | + |
| 54 | +// This is the callback of our shading function. |
| 55 | +// info: color and slope information |
| 56 | +// inData: contains a single float that gives is the current position within the gradient |
| 57 | +// outData: we fill this with the color to display at the given position |
| 58 | +static void JSTShadingFunction(void *infoPtr, const CGFloat *inData, CGFloat *outData) |
| 59 | +{ |
| 60 | + JSTFunctionInfo info = *(JSTFunctionInfo*)infoPtr; // Info struct with colors and parameters |
| 61 | + float p = inData[0]; // Position in gradient |
| 62 | + float q = JSTSlope(p, info.slopeFactor); // Slope value |
| 63 | + outData[0] = info.startColor[0] + (info.endColor[0] - info.startColor[0])*q; |
| 64 | + outData[1] = info.startColor[1] + (info.endColor[1] - info.startColor[1])*q; |
| 65 | + outData[2] = info.startColor[2] + (info.endColor[2] - info.startColor[2])*q; |
| 66 | + outData[3] = info.startColor[3] + (info.endColor[3] - info.startColor[3])*q; |
| 67 | +} |
| 68 | + |
| 69 | +@implementation RCTGradient |
| 70 | +{ |
| 71 | + CGColorSpaceRef _colorSpace; |
| 72 | + CGFloat _startColorComps[4]; |
| 73 | + CGFloat _endColorComps[4]; |
| 74 | + CGFunctionRef _function; |
| 75 | + JSTFunctionInfoRef _functionInfo; |
| 76 | + BOOL _opaque; |
| 77 | +} |
| 78 | + |
| 79 | +- (instancetype)init |
| 80 | +{ |
| 81 | + return [self initWithFrame:CGRectZero]; |
| 82 | +} |
| 83 | + |
| 84 | +- (instancetype)initWithFrame:(CGRect)frame |
| 85 | +{ |
| 86 | + if (self = [super initWithFrame:frame]) { |
| 87 | + _colorSpace = CGColorSpaceCreateDeviceRGB(); |
| 88 | + _startColor = [NSColor clearColor]; |
| 89 | + _endColor = [NSColor clearColor]; |
| 90 | + _startPoint = CGPointMake(0, 0.5); |
| 91 | + _endPoint = CGPointMake(1, 0.5); |
| 92 | + _slopeFactor = 1; |
| 93 | + } |
| 94 | + return self; |
| 95 | +} |
| 96 | + |
| 97 | +RCT_NOT_IMPLEMENTED(- (instancetype)initWithCoder:unused) |
| 98 | + |
| 99 | +- (void)dealloc |
| 100 | +{ |
| 101 | + JSTFunctionInfoRelease(_functionInfo); |
| 102 | + CGFunctionRelease(_function); |
| 103 | + CGColorSpaceRelease(_colorSpace); |
| 104 | +} |
| 105 | + |
| 106 | +- (void)setStartColor:(NSColor *)startColor |
| 107 | +{ |
| 108 | + if (_startColor == startColor) return; |
| 109 | + _startColor = startColor; |
| 110 | + |
| 111 | + [startColor getRed:_startColorComps green:_startColorComps+1 blue:_startColorComps+2 alpha:_startColorComps+3]; |
| 112 | +} |
| 113 | + |
| 114 | +- (void)setEndColor:(NSColor *)endColor |
| 115 | +{ |
| 116 | + if (_endColor == endColor) return; |
| 117 | + _endColor = endColor; |
| 118 | + |
| 119 | + [endColor getRed:_endColorComps green:_endColorComps+1 blue:_endColorComps+2 alpha:_endColorComps+3]; |
| 120 | +} |
| 121 | + |
| 122 | +- (void)didSetProps:(NSArray<NSString *> *)changedProps |
| 123 | +{ |
| 124 | + for (NSString *prop in changedProps) { |
| 125 | + if ([prop isEqualToString:@"startColor"] || |
| 126 | + [prop isEqualToString:@"endColor"] || |
| 127 | + [prop isEqualToString:@"slopeFactor"]) { |
| 128 | + [self _createShadingFunction]; |
| 129 | + break; |
| 130 | + } |
| 131 | + } |
| 132 | + [self setNeedsDisplay:YES]; |
| 133 | +} |
| 134 | + |
| 135 | +- (BOOL)isOpaque |
| 136 | +{ |
| 137 | + return _endColorComps[3] == 1 && _startColorComps[3] == 1; |
| 138 | +} |
| 139 | + |
| 140 | +- (void)_createShadingFunction |
| 141 | +{ |
| 142 | + // Shading function info |
| 143 | + JSTFunctionInfoRelease(_functionInfo); |
| 144 | + _functionInfo = JSTFunctionInfoCreate(); |
| 145 | + memcpy(_functionInfo->startColor, _startColorComps, sizeof(CGFloat)*4); |
| 146 | + memcpy(_functionInfo->endColor, _endColorComps, sizeof(CGFloat)*4); |
| 147 | + _functionInfo->slopeFactor = _slopeFactor; |
| 148 | + |
| 149 | + // Define the shading callbacks |
| 150 | + CGFunctionCallbacks callbacks = {0, JSTShadingFunction, NULL}; |
| 151 | + |
| 152 | + // As input to our function we want 1 value in the range [0.0, 1.0]. |
| 153 | + // This is our position within the gradient. |
| 154 | + size_t domainDimension = 1; |
| 155 | + CGFloat domain[2] = {0.0f, 1.0f}; |
| 156 | + |
| 157 | + // The output of our shading function are 4 values, each in the range [0.0, 1.0]. |
| 158 | + // By specifying 4 ranges here, we limit each color component to that range. Values outside of the range get clipped. |
| 159 | + size_t rangeDimension = 4; |
| 160 | + CGFloat range[8] = { |
| 161 | + 0.0f, 1.0f, // R |
| 162 | + 0.0f, 1.0f, // G |
| 163 | + 0.0f, 1.0f, // B |
| 164 | + 0.0f, 1.0f // A |
| 165 | + }; |
| 166 | + |
| 167 | + // Create the shading function |
| 168 | + CGFunctionRelease(_function); |
| 169 | + _function = CGFunctionCreate(_functionInfo, domainDimension, domain, rangeDimension, range, &callbacks); |
| 170 | +} |
| 171 | + |
| 172 | +- (void)displayLayer:(CALayer *)layer |
| 173 | +{ |
| 174 | + [super displayLayer:layer]; |
| 175 | + |
| 176 | + const CGSize size = self.bounds.size; |
| 177 | + UIGraphicsBeginImageContextWithOptions(size, self.isOpaque, 0.0); |
| 178 | + |
| 179 | + // Preserve contents from super call. |
| 180 | + NSImage *contents = layer.contents; |
| 181 | + [contents drawInRect:self.bounds]; |
| 182 | + |
| 183 | + // Draw the gradient. |
| 184 | + [self drawRect:self.bounds]; |
| 185 | + |
| 186 | + // Fetch the drawing bitmap. |
| 187 | + contents = UIGraphicsGetImageFromCurrentImageContext(); |
| 188 | + UIGraphicsEndImageContext(); |
| 189 | + |
| 190 | + layer.contents = contents; |
| 191 | + layer.magnificationFilter = kCAFilterNearest; |
| 192 | + layer.needsDisplayOnBoundsChange = YES; |
| 193 | +} |
| 194 | + |
| 195 | +- (void)drawRect:(CGRect)rect |
| 196 | +{ |
| 197 | + // Prepare general variables |
| 198 | + CGContextRef context = UIGraphicsGetCurrentContext(); |
| 199 | + |
| 200 | + // Create the shading object |
| 201 | + CGPoint startPoint = CGPointMake(_startPoint.x * rect.size.width, _startPoint.y * rect.size.height); |
| 202 | + CGPoint endPoint = CGPointMake(_endPoint.x * rect.size.width, _endPoint.y * rect.size.height); |
| 203 | + CGShadingRef shading = CGShadingCreateAxial(_colorSpace, startPoint, endPoint, _function, _drawsBeforeStart, _drawsAfterEnd); |
| 204 | + |
| 205 | + // Draw the shading |
| 206 | + CGContextDrawShading(context, shading); |
| 207 | + |
| 208 | + // Clean up |
| 209 | + CGShadingRelease(shading); |
| 210 | +} |
| 211 | + |
| 212 | +@end |
0 commit comments