/*! * iOS SDK * * Tencent is pleased to support the open source community by making * NativeRender 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 "HPAsserts.h" #import "HPToolUtils.h" #import "NativeRenderBorderDrawing.h" #import "NativeRenderGradientObject.h" @interface NativeRenderGradientLocationParser () { NSPointerArray *_locations; NSInteger _previousSettedLocation; } @end @implementation NativeRenderGradientLocationParser - (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 { NSAssert(location < _locationsCount, @"NativeRenderGradientLocationParser 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 *) computeLocations { if (_locations > 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 nil; } @end @implementation NativeRenderGradientObject - (void)drawInContext:(CGContextRef)context canvasInfo:(CanvasInfo)canvasInfo { switch (self.gradientType) { case NativeRenderGradientTypeLinear: NativeRenderDrawLinearGradientInContext(self, context, canvasInfo); break; case NativeRenderGradientTypeRadial: NativeRenderDrawRadialGradientInContext(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, NativeRenderGradientDirection *direction) { BOOL isDrawnByDirection = NO; if (direction) { if ([string isEqualToString:@"totop"]) { *direction = NativeRenderGradientDirectionTop; isDrawnByDirection = YES; } else if ([string isEqualToString:@"totopright"]) { *direction = NativeRenderGradientDirectionTopRight; isDrawnByDirection = YES; } else if ([string isEqualToString:@"toright"]) { *direction = NativeRenderGradientDirectionRight; isDrawnByDirection = YES; } else if ([string isEqualToString:@"tobottomright"]) { *direction = NativeRenderGradientDirectionBottomRight; isDrawnByDirection = YES; } else if ([string isEqualToString:@"tobottom"]) { *direction = NativeRenderGradientDirectionBottom; isDrawnByDirection = YES; } else if ([string isEqualToString:@"tobottomleft"]) { *direction = NativeRenderGradientDirectionBottomLeft; isDrawnByDirection = YES; } else if ([string isEqualToString:@"toleft"]) { *direction = NativeRenderGradientDirectionLeft; isDrawnByDirection = YES; } else if ([string isEqualToString:@"totopleft"]) { *direction = NativeRenderGradientDirectionTopLeft; isDrawnByDirection = YES; } } return isDrawnByDirection; } static CGFloat degreeFromDirection(NativeRenderGradientDirection direction, CGSize size) { CGFloat degree = 0.0f; switch (direction) { case NativeRenderGradientDirectionTopRight: degree = atan(size.height / size.width); break; case NativeRenderGradientDirectionRight: degree = M_PI_2; break; case NativeRenderGradientDirectionBottomRight: degree = M_PI - atan(size.height / size.width); break; case NativeRenderGradientDirectionBottom: degree = M_PI; break; case NativeRenderGradientDirectionBottomLeft: degree = M_PI + atan(size.height / size.width); break; case NativeRenderGradientDirectionLeft: degree = M_PI + M_PI_2; break; case NativeRenderGradientDirectionTopLeft: degree = M_PI + M_PI - atan(size.height / size.width); break; default: break; } return degree * 180.f / M_PI; } static LinearGradientPoints pointsFromDirection(NativeRenderGradientObject *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"]; NativeRenderGradientDirection direction; if (getDirectionFromString(angleString, &direction)) { self.direction = direction; } else { self.degree = [angleString intValue]; self.drawnByDegree = YES; } NSArray *colorStopList = [object objectForKey:@"colorStopList"]; NSMutableArray *colors = [NSMutableArray arrayWithCapacity:[colorStopList count]]; NativeRenderGradientLocationParser *locationParser = [[NativeRenderGradientLocationParser alloc] initWithLocationsCount:[colorStopList count]]; for (NSUInteger i = 0; i < [colorStopList count]; i++) { NSDictionary *colorStop = [colorStopList objectAtIndex:i]; NSNumber *colorNumber = [colorStop objectForKey:@"color"]; UIColor *color = HPConvertNumberToColor([colorNumber integerValue]); [colors addObject:color]; NSNumber *stop = [colorStop objectForKey:@"ratio"]; if (stop) { [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 = HPErrorWithMessageAndModuleName(errorString, nil); HPFatal(error, nil); return self; } } return self; } - (LinearGradientPoints)linearGradientPointsFromSize:(CGSize)size { LinearGradientPoints points = {CGPointZero, CGPointZero}; 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 *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 * 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; } void NativeRenderDrawLinearGradientInContext(NativeRenderGradientObject *object, CGContextRef context, CanvasInfo canvasInfo) { NSCAssert(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 = NativeRenderPathCreateOuterOutline(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); } HP_EXTERN void NativeRenderDrawRadialGradientInContext(NativeRenderGradientObject *object, CGContextRef context, CGSize size) { NSCAssert(context, @"context cannot be null for drawing radial gradient"); NSCAssert(NO, @"NativeRenderDrawRadialGradientInContext not implemented"); }