mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-19 05:51:35 +08:00
362 lines
14 KiB
Objective-C
362 lines
14 KiB
Objective-C
/*!
|
|
* iOS SDK
|
|
*
|
|
* Tencent is pleased to support the open source community by making
|
|
* Hippy available.
|
|
*
|
|
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
|
|
* All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#import "HippyGradientObject.h"
|
|
#import "HippyUtils.h"
|
|
#import "HippyBorderDrawing.h"
|
|
|
|
@interface HippyGradientLocationParser () {
|
|
NSPointerArray *_locations;
|
|
NSInteger _previousSettedLocation;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation HippyGradientLocationParser
|
|
|
|
- (instancetype)initWithLocationsCount:(NSUInteger)count {
|
|
self = [super init];
|
|
if (self) {
|
|
[self setLocationsCount:count];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)setLocationsCount:(NSUInteger)locationsCount {
|
|
_locationsCount = locationsCount;
|
|
_locations = [NSPointerArray strongObjectsPointerArray];
|
|
_locations.count = locationsCount;
|
|
_previousSettedLocation = -1;
|
|
}
|
|
|
|
- (void)setLocationValue:(NSNumber *)value atLocation:(NSUInteger)location {
|
|
HippyAssert(location < _locationsCount, @"HippyGradientLocationParser location out of range, \
|
|
try to insert value at %lu, but count is %lu", location, _locationsCount);
|
|
[_locations replacePointerAtIndex:location withPointer:(__bridge void *)value];
|
|
}
|
|
|
|
- (void)setLocationValuesBetweenLocations1:(NSUInteger)location1 location2:(NSUInteger)location2 {
|
|
if (location2 - location1 < 1) {
|
|
return;
|
|
}
|
|
CGFloat value1 = [(__bridge NSNumber *)[_locations pointerAtIndex:location1] floatValue];
|
|
CGFloat value2 = [(__bridge NSNumber *)[_locations pointerAtIndex:location2] floatValue];
|
|
CGFloat valueDiff = value2 - value1;
|
|
CGFloat locationDiff = location2 - location1;
|
|
CGFloat equalValueForEach = valueDiff / locationDiff;
|
|
for (NSUInteger i = location1 + 1; i < location2; i++) {
|
|
CGFloat value = value1 + (i - location1) * equalValueForEach;
|
|
NSNumber *num = @(value);
|
|
[_locations replacePointerAtIndex:i withPointer:(__bridge void *)num];
|
|
}
|
|
}
|
|
|
|
- (NSArray<NSNumber *> *)computeLocations {
|
|
if (_locations.count > 0) {
|
|
void *first = [_locations pointerAtIndex:0];
|
|
if (!first) {
|
|
NSNumber *num = @(0);
|
|
[_locations replacePointerAtIndex:0 withPointer:(__bridge void *)num];
|
|
}
|
|
NSUInteger lastIndex = [_locations count] - 1;
|
|
void *last = [_locations pointerAtIndex:lastIndex];
|
|
if (!last) {
|
|
NSNumber *num = @(1);
|
|
[_locations replacePointerAtIndex:lastIndex withPointer:(__bridge void *)num];
|
|
}
|
|
NSUInteger settedIndex = 0;
|
|
for (NSUInteger i = 1; i <= lastIndex; i++) {
|
|
const void *p = [_locations pointerAtIndex:i];
|
|
if (p) {
|
|
[self setLocationValuesBetweenLocations1:settedIndex location2:i];
|
|
settedIndex = i;
|
|
}
|
|
}
|
|
return [_locations allObjects];
|
|
}
|
|
return NSArray.array;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation HippyGradientObject
|
|
|
|
- (void)drawInContext:(CGContextRef)context canvasInfo:(CanvasInfo)canvasInfo {
|
|
switch (self.gradientType) {
|
|
case HippyGradientTypeLinear:
|
|
HippyDrawLinearGradientInContext(self, context, canvasInfo);
|
|
break;
|
|
case HippyGradientTypeRadial:
|
|
HippyDrawRadialGradientInContext(self, context, canvasInfo.size);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//get the symmetric point to the 'anchor' point
|
|
static CGPoint oppositePointToAnchorPoint(CGPoint point, CGPoint anchor) {
|
|
CGFloat x = anchor.x - (point.x - anchor.x);
|
|
CGFloat y = anchor.y - (point.y - anchor.y);
|
|
return CGPointMake(x, y);
|
|
}
|
|
|
|
static CGPoint oppositePointToHorizontalAnchorPoint(CGPoint point, CGPoint anchor) {
|
|
CGFloat x = point.x;
|
|
CGFloat y = anchor.y + (anchor.y - point.y);
|
|
return CGPointMake(x, y);
|
|
}
|
|
|
|
static LinearGradientPoints gradientPointsWithSizeAndDegree(CGSize size, NSInteger degree) {
|
|
CGFloat halfWidth = size.width / 2.f;
|
|
CGFloat halfHeight = size.height / 2.f;
|
|
CGFloat diagonalLen = sqrt(size.width * size.width + size.height * size.height) / 2.f;
|
|
CGFloat diagonalRad = atan(size.width / size.height);
|
|
CGFloat rad = degree * M_PI / 180.f;
|
|
CGPoint anchorPoint = CGPointMake(size.width / 2.f, size.height / 2.f);
|
|
CGPoint endPoint = CGPointZero;
|
|
CGPoint startPoint = CGPointZero;
|
|
if (degree >= 0 && degree < 90) {
|
|
if (rad <= diagonalRad) {
|
|
CGFloat diffRad = diagonalRad - rad;
|
|
CGFloat sideLen = diagonalLen * cos(diffRad);
|
|
CGFloat x = sin(rad) * sideLen;
|
|
CGFloat y = cos(rad) * sideLen;
|
|
endPoint = CGPointMake(halfWidth + x, halfHeight - y);
|
|
}
|
|
else {
|
|
CGFloat diffRad1 = rad - diagonalRad;
|
|
CGFloat diffLen = diagonalLen / cos(diffRad1);
|
|
CGFloat diffRad2 = M_PI_2 - rad;
|
|
CGFloat ySideLen = diffLen * tan(diffRad2) / cos(diffRad2);
|
|
CGFloat y = halfHeight - ySideLen;
|
|
CGFloat xSideLen = ySideLen / tan(diffRad2);
|
|
CGFloat x = halfWidth + xSideLen;
|
|
endPoint = CGPointMake(x, y);
|
|
}
|
|
startPoint = oppositePointToAnchorPoint(endPoint, anchorPoint);
|
|
}
|
|
else if (90 == degree) {
|
|
endPoint = CGPointMake(size.width, size.height / 2.f);
|
|
startPoint = CGPointMake(0, size.height / 2.f);
|
|
}
|
|
else if (degree > 90 && degree < 180) {
|
|
LinearGradientPoints point = gradientPointsWithSizeAndDegree(size, 180 - degree);
|
|
endPoint = oppositePointToHorizontalAnchorPoint(point.endPoint, anchorPoint);
|
|
startPoint = oppositePointToAnchorPoint(endPoint, anchorPoint);
|
|
}
|
|
else if (degree >= 180 && degree < 360) {
|
|
LinearGradientPoints points = gradientPointsWithSizeAndDegree(size, degree - 180);
|
|
startPoint = points.endPoint;
|
|
endPoint = points.startPoint;
|
|
}
|
|
return (LinearGradientPoints){startPoint, endPoint};
|
|
}
|
|
|
|
static BOOL getDirectionFromString(NSString *string, HippyGradientDirection *direction) {
|
|
BOOL isDrawnByDirection = NO;
|
|
if (direction) {
|
|
if ([string isEqualToString:@"totop"]) {
|
|
*direction = HippyGradientDirectionTop;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
else if ([string isEqualToString:@"totopright"]) {
|
|
*direction = HippyGradientDirectionTopRight;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
else if ([string isEqualToString:@"toright"]) {
|
|
*direction = HippyGradientDirectionRight;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
else if ([string isEqualToString:@"tobottomright"]) {
|
|
*direction = HippyGradientDirectionBottomRight;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
else if ([string isEqualToString:@"tobottom"]) {
|
|
*direction = HippyGradientDirectionBottom;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
else if ([string isEqualToString:@"tobottomleft"]) {
|
|
*direction = HippyGradientDirectionBottomLeft;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
else if ([string isEqualToString:@"toleft"]) {
|
|
*direction = HippyGradientDirectionLeft;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
else if ([string isEqualToString:@"totopleft"]) {
|
|
*direction = HippyGradientDirectionTopLeft;
|
|
isDrawnByDirection = YES;
|
|
}
|
|
}
|
|
return isDrawnByDirection;
|
|
}
|
|
|
|
static CGFloat degreeFromDirection(HippyGradientDirection direction, CGSize size) {
|
|
CGFloat degree = 0.0f;
|
|
switch (direction) {
|
|
case HippyGradientDirectionTopRight:
|
|
degree = atan(size.height / size.width);
|
|
break;
|
|
case HippyGradientDirectionRight:
|
|
degree = M_PI_2;
|
|
break;
|
|
case HippyGradientDirectionBottomRight:
|
|
degree = M_PI - atan(size.height / size.width);
|
|
break;
|
|
case HippyGradientDirectionBottom:
|
|
degree = M_PI;
|
|
break;
|
|
case HippyGradientDirectionBottomLeft:
|
|
degree = M_PI + atan(size.height / size.width);
|
|
break;
|
|
case HippyGradientDirectionLeft:
|
|
degree = M_PI + M_PI_2;
|
|
break;
|
|
case HippyGradientDirectionTopLeft:
|
|
degree = M_PI + M_PI - atan(size.height / size.width);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return degree * 180.f / M_PI;
|
|
}
|
|
|
|
static LinearGradientPoints pointsFromDirection(HippyGradientObject *object, CGSize size) {
|
|
return gradientPointsWithSizeAndDegree(size, degreeFromDirection(object.direction, size));
|
|
}
|
|
|
|
- (instancetype)initWithGradientObject:(NSDictionary *)object {
|
|
self = [super init];
|
|
if (self) {
|
|
// style.linearGradient = {
|
|
// // @param {string} angle - degree for linear gradient line.
|
|
// angle: '45',
|
|
// /**
|
|
// * @param {Array} colorStopList - colors & stops
|
|
// */
|
|
// colorStopList: [{ color: -11756806, ratio: 0.1 }, { color: -11756806, ratio: 0.6 }],
|
|
// };
|
|
@try {
|
|
NSString *angleString = [object objectForKey:@"angle"];
|
|
HippyGradientDirection direction;
|
|
if (getDirectionFromString(angleString, &direction)) {
|
|
self.direction = direction;
|
|
}
|
|
else {
|
|
self.degree = [angleString intValue];
|
|
self.drawnByDegree = YES;
|
|
}
|
|
|
|
NSArray<NSDictionary *> *colorStopList = [object objectForKey:@"colorStopList"];
|
|
NSMutableArray<UIColor *> *colors = [NSMutableArray arrayWithCapacity:[colorStopList count]];
|
|
HippyGradientLocationParser *locationParser = [[HippyGradientLocationParser alloc] initWithLocationsCount:[colorStopList count]];
|
|
for (NSUInteger i = 0; i < [colorStopList count]; i++) {
|
|
NSDictionary *colorStop = [colorStopList objectAtIndex:i];
|
|
NSNumber *colorNumber = [colorStop objectForKey:@"color"];
|
|
UIColor *color = HippyConvertNumberToColor([colorNumber integerValue]);
|
|
[colors addObject:color];
|
|
NSNumber *stop = [colorStop objectForKey:@"ratio"];
|
|
if (stop != nil) {
|
|
[locationParser setLocationValue:stop atLocation:i];
|
|
}
|
|
}
|
|
self.colors = [colors copy];
|
|
self.locations = [locationParser computeLocations];
|
|
} @catch (NSException *exception) {
|
|
NSString *errorString = [NSString stringWithFormat:@"gradient parse error:%@", [exception reason]];
|
|
NSError *error = HippyErrorWithMessage(errorString);
|
|
HippyFatal(error);
|
|
return self;
|
|
}
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (LinearGradientPoints)linearGradientPointsFromSize:(CGSize)size {
|
|
LinearGradientPoints points;
|
|
if (self.drawnByDegree) {
|
|
self.degree %= 360;
|
|
if (self.degree < 0) {
|
|
self.degree += 360;
|
|
}
|
|
points = gradientPointsWithSizeAndDegree(size, self.degree);
|
|
}
|
|
else {
|
|
points = pointsFromDirection(self, size);
|
|
}
|
|
return points;
|
|
}
|
|
|
|
@end
|
|
|
|
static CFArrayRef UIColorsToCGColors(NSArray<UIColor *> *colors) {
|
|
NSMutableArray *CGColorsArray = [NSMutableArray arrayWithCapacity:[colors count]];
|
|
for (UIColor *color in colors) {
|
|
[CGColorsArray addObject:(id)color.CGColor];
|
|
}
|
|
return (__bridge CFArrayRef)[NSArray arrayWithArray:CGColorsArray];
|
|
}
|
|
|
|
static CGFloat *CreateNSNumbersToCGFloats(NSArray<NSNumber *> * locations) {
|
|
CGFloat *pLocs = malloc([locations count] * sizeof(CGFloat));
|
|
for (long i = 0; i < [locations count]; i++) {
|
|
NSNumber *num = locations[i];
|
|
pLocs[i] = [num doubleValue];
|
|
}
|
|
return pLocs;
|
|
}
|
|
|
|
HIPPY_EXTERN void HippyDrawLinearGradientInContext(HippyGradientObject *object, CGContextRef context, CanvasInfo canvasInfo) {
|
|
HippyAssert(context, @"context cannot be null for drawing linear gradient");
|
|
if (nil == context) {
|
|
return;
|
|
}
|
|
CGColorSpaceRef spaceRef = CGColorSpaceCreateDeviceRGB();
|
|
CFArrayRef colors = UIColorsToCGColors(object.colors);
|
|
CGFloat *locations = CreateNSNumbersToCGFloats(object.locations);
|
|
CGGradientRef gradient = CGGradientCreateWithColors(spaceRef, colors, locations);
|
|
|
|
CGContextSaveGState(context);
|
|
CGSize size = canvasInfo.size;
|
|
CGPathRef pathRef = HippyPathCreateOuterOutline(NO, CGRectMake(0, 0, size.width, size.height), canvasInfo.cornerRadii);
|
|
CGContextAddPath(context, pathRef);
|
|
CGContextClip(context);
|
|
LinearGradientPoints points = [object linearGradientPointsFromSize:size];
|
|
CGContextDrawLinearGradient(context,
|
|
gradient,
|
|
points.startPoint,
|
|
points.endPoint,
|
|
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation
|
|
);
|
|
CGPathRelease(pathRef);
|
|
CGGradientRelease(gradient);
|
|
free(locations);
|
|
CGColorSpaceRelease(spaceRef);
|
|
CGContextRestoreGState(context);
|
|
}
|
|
|
|
HIPPY_EXTERN void HippyDrawRadialGradientInContext(HippyGradientObject *object, CGContextRef context, CGSize size) {
|
|
HippyAssert(context, @"context cannot be null for drawing radial gradient");
|
|
HippyAssert(NO, @"HippyDrawRadialGradientInContext not implemented");
|
|
}
|