From b1f5d6b34d82cb66cebdaccf07e75e119fc8d53a Mon Sep 17 00:00:00 2001 From: Curtis Hard Date: Fri, 11 Dec 2015 15:12:48 +0000 Subject: [PATCH] Improved gradient support and transforms Much improved gradient support - even with radial. Also fixed various issues with transforms such as rotating around a point. Still unsure about gradientTransform rotate units as they are specified in fractions and not points... --- .../UserInterfaceState.xcuserstate | Bin 40655 -> 41226 bytes source/IJSVG.m | 24 +---- source/IJSVGColor.m | 1 + source/IJSVGGradient.h | 10 ++ source/IJSVGGradient.m | 46 +++++++- source/IJSVGLinearGradient.h | 4 +- source/IJSVGLinearGradient.m | 28 +++++ source/IJSVGNode.h | 2 - source/IJSVGNode.m | 3 - source/IJSVGParser.m | 58 +++++++--- source/IJSVGRadialGradient.h | 4 +- source/IJSVGRadialGradient.m | 43 +++++++- source/IJSVGTransform.h | 2 + source/IJSVGTransform.m | 100 +++++++++++++++++- 14 files changed, 280 insertions(+), 45 deletions(-) diff --git a/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate b/IJSVGExample/IJSVGExample.xcodeproj/project.xcworkspace/xcuserdata/curtishard.xcuserdatad/UserInterfaceState.xcuserstate index fe0baa3079ba911f47dabd0f0cf756b511dd3e41..94ed56849a14bceeb598cb8ea0740a8fc16d5faf 100644 GIT binary patch literal 41226 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%0Da^T0CJ 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 EU!DOlUjP6A literal 40655 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 #import "IJSVGDef.h" +#import "IJSVGTransform.h" @interface IJSVGGradient : IJSVGDef { NSGradient * gradient; + CGGradientRef CGGradient; CGFloat angle; + CGPoint startPoint; + CGPoint endPoint; } @property ( nonatomic, retain ) NSGradient * gradient; @property ( nonatomic, assign ) CGFloat angle; +@property ( nonatomic, assign ) CGPoint startPoint; +@property ( nonatomic, assign ) CGPoint endPoint; +@property ( nonatomic, assign ) CGGradientRef CGGradient; + (CGFloat *)computeColorStopsFromString:(NSXMLElement *)element colors:(NSArray **)someColors; +- (CGGradientRef)CGGradient; +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path; @end diff --git a/source/IJSVGGradient.m b/source/IJSVGGradient.m index 9f8f293..ff95bb0 100644 --- a/source/IJSVGGradient.m +++ b/source/IJSVGGradient.m @@ -10,12 +10,14 @@ @implementation IJSVGGradient -@synthesize gradient; -@synthesize angle; +@synthesize gradient, CGGradient; +@synthesize angle, startPoint, endPoint; - (void)dealloc { [gradient release], gradient = nil; + if( CGGradient != nil ) + CGGradientRelease(CGGradient); [super dealloc]; } @@ -23,6 +25,8 @@ { IJSVGGradient * clone = [super copyWithZone:zone]; clone.gradient = [[self.gradient copy] autorelease]; + clone.startPoint = self.startPoint; + clone.endPoint = self.endPoint; return clone; } @@ -55,6 +59,15 @@ NSColor * stopColor = [IJSVGColor colorFromHEXString:[[stop attributeForName:@"stop-color"] stringValue] alpha:stopOpacity]; + // no hex, grab it from predefined + if( stopColor == nil ) + { + stopColor = [IJSVGColor colorFromPredefinedColorName:[[stop attributeForName:@"stop-color"] stringValue]]; + if( stopColor != nil && stopOpacity != 1.f ) + stopColor = [IJSVGColor changeAlphaOnColor:stopColor + to:stopOpacity]; + } + // add it into the array if( stopColor != nil ) [(NSMutableArray *)colors addObject:stopColor]; @@ -84,4 +97,33 @@ return stopsParams; } +- (CGGradientRef)CGGradient +{ + // store it in the cache + if(CGGradient != nil) + return CGGradient; + + // actually create the gradient + NSInteger num = self.gradient.numberOfColorStops; + CGFloat * locations = malloc(sizeof(CGFloat)*num); + CFMutableArrayRef colors = CFArrayCreateMutable(kCFAllocatorDefault, (CFIndex)num, &kCFTypeArrayCallBacks); + for( NSInteger i = 0; i < num; i++ ) + { + NSColor * color; + [self.gradient getColor:&color + location:&locations[i] + atIndex:i]; + CFArrayAppendValue(colors, color.CGColor); + } + CGGradientRef result = CGGradientCreateWithColors(self.gradient.colorSpace.CGColorSpace, colors, locations); + CFRelease(colors); + free(locations); + return CGGradient = result; +} + +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path +{ +} + @end diff --git a/source/IJSVGLinearGradient.h b/source/IJSVGLinearGradient.h index 5601bd0..57c8d7d 100644 --- a/source/IJSVGLinearGradient.h +++ b/source/IJSVGLinearGradient.h @@ -12,6 +12,8 @@ @interface IJSVGLinearGradient : IJSVGGradient + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGLinearGradient *)aGradient; + gradient:(IJSVGLinearGradient *)aGradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint; @end diff --git a/source/IJSVGLinearGradient.m b/source/IJSVGLinearGradient.m index d7bc0a5..95e15b2 100644 --- a/source/IJSVGLinearGradient.m +++ b/source/IJSVGLinearGradient.m @@ -13,6 +13,8 @@ + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGLinearGradient *)aGradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint { // assume its a vertical / horizonal CGFloat x1 = [[[element attributeForName:@"x1"] stringValue] floatValue]; @@ -20,6 +22,9 @@ CGFloat y1 = [[[element attributeForName:@"y1"] stringValue] floatValue]; CGFloat y2 = [[[element attributeForName:@"y2"] stringValue] floatValue]; + *startPoint = CGPointMake( x1, y1); + *endPoint = CGPointMake( x2, y2); + // horizontal if( y1 == y2 && x1 != x2 ) aGradient.angle = 0.f; @@ -49,4 +54,27 @@ return grad; } +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path +{ + // grab the start and end point + CGPoint aStartPoint = self.startPoint; + CGPoint aEndPoint = self.endPoint; + + // convert the nsgradient to a CGGradient + CGGradientRef gRef = [self CGGradient]; + + // apply transform for each point + for( IJSVGTransform * transform in self.transforms ) + { + CGAffineTransform trans = transform.CGAffineTransform; + aStartPoint = CGPointApplyAffineTransform(aStartPoint, trans); + aEndPoint = CGPointApplyAffineTransform(aEndPoint, trans); + } + + // draw the gradient + CGGradientDrawingOptions opt = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; + CGContextDrawLinearGradient(ctx, gRef, aStartPoint, aEndPoint, opt); +} + @end diff --git a/source/IJSVGNode.h b/source/IJSVGNode.h index 6e7881c..78b0558 100644 --- a/source/IJSVGNode.h +++ b/source/IJSVGNode.h @@ -88,7 +88,6 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; IJSVGNode * parentNode; IJSVGGroup * clipPath; NSArray * transforms; - NSArray * gradientTransforms; IJSVGWindingRule windingRule; IJSVGLineCapStyle lineCapStyle; @@ -120,7 +119,6 @@ static CGFloat IJSVGInheritedFloatValue = -99.9999991; @property ( nonatomic, assign ) IJSVGLineCapStyle lineCapStyle; @property ( nonatomic, assign ) IJSVGLineJoinStyle lineJoinStyle; @property ( nonatomic, retain ) NSArray * transforms; -@property ( nonatomic, retain ) NSArray * gradientTransforms; @property ( nonatomic, retain ) IJSVGDef * def; @property ( nonatomic, retain ) IJSVGGradient * fillGradient; @property ( nonatomic, assign ) CGFloat * strokeDashArray; diff --git a/source/IJSVGNode.m b/source/IJSVGNode.m index 7c238d1..88dabd1 100644 --- a/source/IJSVGNode.m +++ b/source/IJSVGNode.m @@ -28,7 +28,6 @@ @synthesize identifier; @synthesize parentNode; @synthesize transforms; -@synthesize gradientTransforms; @synthesize windingRule; @synthesize def; @synthesize fillGradient; @@ -46,7 +45,6 @@ [unicode release], unicode = nil; [fillGradient release], fillGradient = nil; [transforms release], transforms = nil; - [gradientTransforms release], gradientTransforms = nil; [fillColor release], fillColor = nil; [strokeColor release], strokeColor = nil; [identifier release], identifier = nil; @@ -127,7 +125,6 @@ self.usesDefaultFillColor = node.usesDefaultFillColor; self.transforms = node.transforms; - self.gradientTransforms = node.gradientTransforms; self.def = node.def; self.windingRule = node.windingRule; self.lineCapStyle = node.lineCapStyle; diff --git a/source/IJSVGParser.m b/source/IJSVGParser.m index af7bc41..3c3aeee 100644 --- a/source/IJSVGParser.m +++ b/source/IJSVGParser.m @@ -117,8 +117,7 @@ *error = [[[NSError alloc] initWithDomain:IJSVGErrorDomain code:code userInfo:nil] autorelease]; - if( _document != nil ) - [_document release], _document = nil; + [_document release], _document = nil; [self release], self = nil; return nil; } @@ -130,7 +129,8 @@ return YES; // check the viewbox - if( NSEqualRects( self.viewBox, NSZeroRect ) ) + if( NSEqualRects( self.viewBox, NSZeroRect ) || + self.size.width == 0 || self.size.height == 0 ) { if( error != NULL ) *error = [[[NSError alloc] initWithDomain:IJSVGErrorDomain @@ -165,15 +165,24 @@ // there is no view box so find the width and height CGFloat w = [[[svgElement attributeForName:@"width"] stringValue] floatValue]; CGFloat h = [[[svgElement attributeForName:@"height"] stringValue] floatValue]; + if( h == 0.f && w != 0.f ) + h = w; + else if( w == 0.f && h != 0.f ) + w = h; viewBox = NSMakeRect( 0.f, 0.f, w, h ); } + // parse the width and height.... CGFloat w = [[[svgElement attributeForName:@"width"] stringValue] floatValue]; CGFloat h = [[[svgElement attributeForName:@"height"] stringValue] floatValue]; if( w == 0.f && h == 0.f ) { w = viewBox.size.width; h = viewBox.size.height; + } else if( w == 0 && h != 0.f ) { + w = viewBox.size.width; + } else if( h == 0 && w != 0.f ) { + h = viewBox.size.height; } proposedViewSize = NSMakeSize( w, h ); @@ -369,7 +378,7 @@ if( defID != nil ) { IJSVGGradient * grad = (IJSVGGradient *)[node defForID:defID]; node.fillGradient = grad; - node.gradientTransforms = grad.gradientTransforms; + grad.transforms = grad.transforms; } else { // change the fill color over if its allowed node.fillColor = [IJSVGColor colorFromString:[fillAttribute stringValue]]; @@ -391,9 +400,9 @@ { NSMutableArray * tran = [[[NSMutableArray alloc] init] autorelease]; [tran addObjectsFromArray:[IJSVGTransform transformsForString:[gradTransformAttribute stringValue]]]; - if( node.gradientTransforms != nil ) - [tran addObjectsFromArray:node.gradientTransforms]; - node.gradientTransforms = tran; + if( node.transforms != nil ) + [tran addObjectsFromArray:node.transforms]; + node.transforms = tran; } // winding rule @@ -437,7 +446,7 @@ if( defID != nil ) { IJSVGGradient * grad = (IJSVGGradient *)[node defForID:defID]; node.fillGradient = grad; - node.gradientTransforms = grad.gradientTransforms; + node.transforms = grad.transforms; } } else { if( [IJSVGColor computeColor:fill] != nil ) @@ -795,21 +804,34 @@ IJSVGLinearGradient * grad = [[[IJSVGLinearGradient alloc] init] autorelease]; grad.type = aType; [grad applyPropertiesFromNode:node]; + grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; + CGPoint startPoint, endPoint; [IJSVGLinearGradient parseGradient:element - gradient:grad]; + gradient:grad + startPoint:&startPoint + endPoint:&endPoint]; [self _parseElementForCommonAttributes:element node:grad]; + grad.startPoint = startPoint; + grad.endPoint = endPoint; [parentGroup addDef:grad]; continue; } IJSVGLinearGradient * gradient = [[[IJSVGLinearGradient alloc] init] autorelease]; gradient.type = aType; + + CGPoint startPoint, endPoint; gradient.gradient = [IJSVGLinearGradient parseGradient:element - gradient:gradient]; + gradient:gradient + startPoint:&startPoint + endPoint:&endPoint]; + [self _parseElementForCommonAttributes:element node:gradient]; + gradient.startPoint = startPoint; + gradient.endPoint = endPoint; [parentGroup addDef:gradient]; continue; } @@ -827,18 +849,30 @@ grad.type = aType; [grad applyPropertiesFromNode:node]; grad.gradient = [[[(IJSVGGradient *)node gradient] copy] autorelease]; + + CGPoint startPoint, endPoint; [IJSVGRadialGradient parseGradient:element - gradient:grad]; + gradient:grad + startPoint:&startPoint + endPoint:&endPoint]; [self _parseElementForCommonAttributes:element node:grad]; + grad.startPoint = startPoint; + grad.endPoint = endPoint; [parentGroup addDef:grad]; continue; } IJSVGRadialGradient * gradient = [[[IJSVGRadialGradient alloc] init] autorelease]; gradient.type = aType; + + CGPoint startPoint, endPoint; gradient.gradient = [IJSVGRadialGradient parseGradient:element - gradient:gradient]; + gradient:gradient + startPoint:&startPoint + endPoint:&endPoint]; + gradient.startPoint = startPoint; + gradient.endPoint = endPoint; [self _parseElementForCommonAttributes:element node:gradient]; [parentGroup addDef:gradient]; diff --git a/source/IJSVGRadialGradient.h b/source/IJSVGRadialGradient.h index 17e4f68..b9613c2 100644 --- a/source/IJSVGRadialGradient.h +++ b/source/IJSVGRadialGradient.h @@ -26,6 +26,8 @@ @property ( nonatomic, assign ) CGFloat radius; + (NSGradient *)parseGradient:(NSXMLElement *)element - gradient:(IJSVGRadialGradient *)gradient; + gradient:(IJSVGRadialGradient *)gradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint; @end diff --git a/source/IJSVGRadialGradient.m b/source/IJSVGRadialGradient.m index a3e4635..036f004 100644 --- a/source/IJSVGRadialGradient.m +++ b/source/IJSVGRadialGradient.m @@ -16,11 +16,25 @@ @synthesize fy; @synthesize radius; +- (id)copyWithZone:(NSZone *)zone +{ + IJSVGRadialGradient * grad = [super copyWithZone:zone]; + grad.fx = self.fx; + grad.fy = self.fy; + grad.cx = self.cx; + grad.cy = self.cy; + grad.radius = self.radius; + grad.startPoint = self.startPoint; + grad.endPoint = self.endPoint; + return grad; +} + + (NSGradient *)parseGradient:(NSXMLElement *)element gradient:(IJSVGRadialGradient *)gradient + startPoint:(CGPoint *)startPoint + endPoint:(CGPoint *)endPoint { - // assume its a vertical / horizonal gradient.cx = [[[element attributeForName:@"cx"] stringValue] floatValue]/100; gradient.cy = [[[element attributeForName:@"cy"] stringValue] floatValue]/100; @@ -43,6 +57,9 @@ if( gradient.gradient != nil ) return nil; + *startPoint = CGPointMake(gradient.cx, gradient.cy); + *endPoint = CGPointMake(gradient.fx, gradient.fy); + NSArray * colors = nil; CGFloat * colorStops = [[self class] computeColorStopsFromString:element colors:&colors]; NSGradient * ret = [[[NSGradient alloc] initWithColors:colors @@ -52,4 +69,28 @@ return ret; } +- (void)drawInContextRef:(CGContextRef)ctx + path:(IJSVGPath *)path +{ + // apply any transforms to the current context + CGRect bounds = path.path.bounds; + CGContextTranslateCTM( ctx, bounds.origin.x, bounds.origin.y ); + + for( IJSVGTransform * transform in self.transforms ) + { + transform = [[transform copy] autorelease]; + [transform recalculateWithBounds:bounds]; + CGAffineTransform trans = transform.CGAffineTransform; + CGContextConcatCTM(ctx, trans); + } + + CGPoint sp = CGPointMake( bounds.size.width*self.cx, bounds.size.height*self.cy); + CGPoint ep = CGPointMake( bounds.size.width*self.fx, bounds.size.height*self.fy); + + // draw the gradient + CGGradientDrawingOptions options = kCGGradientDrawsBeforeStartLocation|kCGGradientDrawsAfterEndLocation; + CGGradientRef grad = self.CGGradient; + CGContextDrawRadialGradient(ctx, grad, sp, 0.f, ep, bounds.size.height*self.radius, options); +} + @end diff --git a/source/IJSVGTransform.h b/source/IJSVGTransform.h index d00b814..99cedc7 100644 --- a/source/IJSVGTransform.h +++ b/source/IJSVGTransform.h @@ -35,5 +35,7 @@ typedef NS_OPTIONS( NSInteger, IJSVGTransformCommand ) { + (void)performTransform:(IJSVGTransform *)transform inContext:(CGContextRef)context; + (NSBezierPath *)transformedPath:(IJSVGPath *)path; +- (CGAffineTransform)CGAffineTransform; +- (void)recalculateWithBounds:(CGRect)bounds; @end diff --git a/source/IJSVGTransform.m b/source/IJSVGTransform.m index 9fc6e07..8cf1a85 100644 --- a/source/IJSVGTransform.m +++ b/source/IJSVGTransform.m @@ -21,6 +21,32 @@ [super dealloc]; } +- (id)copyWithZone:(NSZone *)zone +{ + IJSVGTransform * trans = [[[self class] alloc] init]; + trans.command = self.command; + trans.parameters = (CGFloat*)malloc(sizeof(CGFloat)*self.parameterCount); + trans.sort = sort; + trans.parameterCount = self.parameterCount; + memcpy( trans.parameters, self.parameters, sizeof(CGFloat)*self.parameterCount); + return trans; +} + +- (void)recalculateWithBounds:(CGRect)bounds +{ + CGFloat max = bounds.size.width>bounds.size.height?bounds.size.width:bounds.size.height; + switch (self.command) { + case IJSVGTransformCommandRotate: { + if( self.parameterCount == 1 ) + return; + self.parameters[1] = self.parameters[1]*max; + self.parameters[2] = self.parameters[2]*max; + } + default: + return; + } +} + + (IJSVGTransformCommand)commandForCommandString:(NSString *)str { if( [str isEqualToString:@"matrix"] ) @@ -135,6 +161,16 @@ case IJSVGTransformCommandRotate: { if( transform.parameterCount == 1 ) [at rotateByDegrees:transform.parameters[0]]; + else { + CGFloat centerX = transform.parameters[1]; + CGFloat centerY = transform.parameters[2]; + CGFloat angle = transform.parameters[0]*(M_PI/180.f); + [at translateXBy:centerX + yBy:centerY]; + [at rotateByRadians:angle]; + [at translateXBy:-1.f*centerX + yBy:-1.f*centerY]; + } break; } @@ -187,8 +223,17 @@ case IJSVGTransformCommandRotate: { // these are in radians, not degrees if( transform.parameterCount == 1 ) + { CGContextRotateCTM( context, (transform.parameters[0] / 180) * M_PI); - // need support for rotate around a point + } else { + // need support for rotate around a point + CGFloat centerX = transform.parameters[1]; + CGFloat centerY = transform.parameters[2]; + CGFloat angle = transform.parameters[0]*(M_PI/180.f); + CGContextTranslateCTM(context, centerX, centerY); + CGContextRotateCTM(context, angle); + CGContextTranslateCTM(context, -1.f*centerX, -1.f*centerY); + } } // do nothing @@ -199,4 +244,57 @@ } +- (CGAffineTransform)CGAffineTransform +{ + switch(self.command) + { + // matrix + case IJSVGTransformCommandMatrix: { + return CGAffineTransformMake( self.parameters[0], + self.parameters[1], + self.parameters[2], + self.parameters[3], + self.parameters[4], + self.parameters[5]); + } + + // translate + case IJSVGTransformCommandTranslate: { + if(self.parameterCount == 1) + return CGAffineTransformMakeTranslation( self.parameters[0], 0 ); + return CGAffineTransformMakeTranslation(self.parameters[0], self.parameters[1]); + } + + // scale + case IJSVGTransformCommandScale: { + if(self.parameterCount == 1) + return CGAffineTransformMakeScale( self.parameters[0], self.parameters[0]); + return CGAffineTransformMakeScale( self.parameters[0], self.parameters[1]); + } + + // rotate + case IJSVGTransformCommandRotate: { + if(self.parameterCount == 1) + return CGAffineTransformMakeRotation((self.parameters[0]/180) * M_PI); + else { + CGFloat centerX = self.parameters[1]; + CGFloat centerY = self.parameters[2]; + CGFloat angle = self.parameters[0]*(M_PI/180.f); + CGAffineTransform def = CGAffineTransformIdentity; + def = CGAffineTransformTranslate(def, centerX, centerY); + def = CGAffineTransformRotate(def, angle); + def = CGAffineTransformTranslate(def, -1.f*centerX, -1.f*centerY); + return def; + } + break; + } + + // do nothing + case IJSVGTransformCommandNotImplemented: { + return CGAffineTransformIdentity; + } + } + return CGAffineTransformIdentity; +} + @end