1/** 2 * Copyright (c) 2015-present, Horcrux. 3 * All rights reserved. 4 * 5 * This source code is licensed under the MIT-style license found in the 6 * LICENSE file in the root directory of this source tree. 7 */ 8 9#import "RNSVGImage.h" 10#import "RCTConvert+RNSVG.h" 11 12#if __has_include(<React/RCTImageLoader.h>) 13 14#import <React/RCTImageLoader.h> 15 16#else 17 18#import <React/RCTImageLoaderProtocol.h> 19#import <React/RCTImageShadowView.h> 20#import <React/RCTImageURLLoader.h> 21#import <React/RCTImageView.h> 22 23#endif // RCT_NEW_ARCH_ENABLED 24 25#import <React/RCTBridge.h> 26#import <React/RCTLog.h> 27#import "RNSVGViewBox.h" 28 29#ifdef RCT_NEW_ARCH_ENABLED 30#import <React/RCTConversions.h> 31#import <React/RCTFabricComponentsPlugins.h> 32#import <React/RCTImageResponseObserverProxy.h> 33#import <React/RCTImageSource.h> 34#import <react/renderer/components/rnsvg/ComponentDescriptors.h> 35#import <react/renderer/components/view/conversions.h> 36#import <react/renderer/imagemanager/RCTImagePrimitivesConversions.h> 37#import <rnsvg/RNSVGImageComponentDescriptor.h> 38#import "RNSVGFabricConversions.h" 39 40using namespace facebook::react; 41#endif // RCT_NEW_ARCH_ENABLED 42 43@implementation RNSVGImage { 44 CGImageRef _image; 45 CGSize _imageSize; 46 RCTImageLoaderCancellationBlock _reloadImageCancellationBlock; 47 48#ifdef RCT_NEW_ARCH_ENABLED 49 RNSVGImageShadowNode::ConcreteState::Shared _state; 50 RCTImageResponseObserverProxy _imageResponseObserverProxy; 51#endif // RCT_NEW_ARCH_ENABLED 52} 53#ifdef RCT_NEW_ARCH_ENABLED 54 55- (instancetype)initWithFrame:(CGRect)frame 56{ 57 if (self = [super initWithFrame:frame]) { 58 static const auto defaultProps = std::make_shared<const RNSVGImageProps>(); 59 _props = defaultProps; 60 61 _imageResponseObserverProxy = RCTImageResponseObserverProxy(self); 62 } 63 return self; 64} 65 66#pragma mark - RCTComponentViewProtocol 67 68+ (ComponentDescriptorProvider)componentDescriptorProvider 69{ 70 return concreteComponentDescriptorProvider<RNSVGImageComponentDescriptor>(); 71} 72 73- (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &)oldProps 74{ 75 const auto &newProps = *std::static_pointer_cast<const RNSVGImageProps>(props); 76 77 self.x = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.x)]; 78 self.y = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.y)]; 79 if (RCTNSStringFromStringNilIfEmpty(newProps.height)) { 80 self.imageheight = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.height)]; 81 } 82 if (RCTNSStringFromStringNilIfEmpty(newProps.width)) { 83 self.imagewidth = [RNSVGLength lengthWithString:RCTNSStringFromString(newProps.width)]; 84 } 85 self.align = RCTNSStringFromStringNilIfEmpty(newProps.align); 86 self.meetOrSlice = intToRNSVGVBMOS(newProps.meetOrSlice); 87 88 setCommonRenderableProps(newProps, self); 89 _props = std::static_pointer_cast<RNSVGImageProps const>(props); 90} 91 92- (void)updateState:(State::Shared const &)state oldState:(State::Shared const &)oldState 93{ 94 RCTAssert(state, @"`state` must not be null."); 95 RCTAssert( 96 std::dynamic_pointer_cast<RNSVGImageShadowNode::ConcreteState const>(state), 97 @"`state` must be a pointer to `RNSVGImageShadowNode::ConcreteState`."); 98 99 auto oldImageState = std::static_pointer_cast<RNSVGImageShadowNode::ConcreteState const>(_state); 100 auto newImageState = std::static_pointer_cast<RNSVGImageShadowNode::ConcreteState const>(state); 101 102 [self _setStateAndResubscribeImageResponseObserver:newImageState]; 103} 104 105- (void)_setStateAndResubscribeImageResponseObserver:(RNSVGImageShadowNode::ConcreteState::Shared const &)state 106{ 107 if (_state) { 108 auto &observerCoordinator = _state->getData().getImageRequest().getObserverCoordinator(); 109 observerCoordinator.removeObserver(_imageResponseObserverProxy); 110 } 111 112 _state = state; 113 114 if (_state) { 115 auto &observerCoordinator = _state->getData().getImageRequest().getObserverCoordinator(); 116 observerCoordinator.addObserver(_imageResponseObserverProxy); 117 } 118} 119 120#pragma mark - RCTImageResponseDelegate 121 122- (void)didReceiveImage:(UIImage *)image metadata:(id)metadata fromObserver:(void const *)observer 123{ 124 if (!_eventEmitter || !_state) { 125 // Notifications are delivered asynchronously and might arrive after the view is already recycled. 126 // In the future, we should incorporate an `EventEmitter` into a separate object owned by `ImageRequest` or `State`. 127 // See for more info: T46311063. 128 return; 129 } 130 dispatch_async(dispatch_get_main_queue(), ^{ 131 self->_image = CGImageRetain(image.CGImage); 132 self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image)); 133 [self invalidate]; 134 }); 135} 136 137- (void)didReceiveProgress:(float)progress fromObserver:(void const *)observer 138{ 139} 140 141- (void)didReceiveFailureFromObserver:(void const *)observer 142{ 143 if (_image) { 144 CGImageRelease(_image); 145 } 146 _image = nil; 147} 148 149- (void)prepareForRecycle 150{ 151 [super prepareForRecycle]; 152 [self _setStateAndResubscribeImageResponseObserver:nullptr]; 153 154 _x = nil; 155 _y = nil; 156 _imageheight = nil; 157 _imagewidth = nil; 158 _src = nil; 159 _align = nil; 160 _meetOrSlice = kRNSVGVBMOSMeet; 161 162 if (_image) { 163 CGImageRelease(_image); 164 } 165 _image = nil; 166 _imageSize = CGSizeZero; 167 _reloadImageCancellationBlock = nil; 168} 169#endif // RCT_NEW_ARCH_ENABLED 170 171- (void)setSrc:(RCTImageSource *)src 172{ 173#ifdef RCT_NEW_ARCH_ENABLED 174#else 175 if (src == _src) { 176 return; 177 } 178 _src = src; 179 CGImageRelease(_image); 180 _image = nil; 181 if (src.size.width != 0 && src.size.height != 0) { 182 _imageSize = src.size; 183 } else { 184 _imageSize = CGSizeMake(0, 0); 185 } 186 187 RCTImageLoaderCancellationBlock previousCancellationBlock = _reloadImageCancellationBlock; 188 if (previousCancellationBlock) { 189 previousCancellationBlock(); 190 _reloadImageCancellationBlock = nil; 191 } 192 193 _reloadImageCancellationBlock = [[self.bridge moduleForName:@"ImageLoader"] 194 loadImageWithURLRequest:src.request 195 callback:^(NSError *error, UIImage *image) { 196 dispatch_async(dispatch_get_main_queue(), ^{ 197 self->_image = CGImageRetain(image.CGImage); 198 self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image)); 199 [self invalidate]; 200 }); 201 }]; 202#endif // RCT_NEW_ARCH_ENABLED 203} 204 205- (void)setX:(RNSVGLength *)x 206{ 207 if ([x isEqualTo:_x]) { 208 return; 209 } 210 [self invalidate]; 211 _x = x; 212} 213 214- (void)setY:(RNSVGLength *)y 215{ 216 if ([y isEqualTo:_y]) { 217 return; 218 } 219 [self invalidate]; 220 _y = y; 221} 222 223- (void)setImagewidth:(RNSVGLength *)width 224{ 225 if ([width isEqualTo:_imagewidth]) { 226 return; 227 } 228 [self invalidate]; 229 _imagewidth = width; 230} 231 232- (void)setImageheight:(RNSVGLength *)height 233{ 234 if ([height isEqualTo:_imageheight]) { 235 return; 236 } 237 [self invalidate]; 238 _imageheight = height; 239} 240 241- (void)setAlign:(NSString *)align 242{ 243 if ([align isEqualToString:_align]) { 244 return; 245 } 246 [self invalidate]; 247 _align = align; 248} 249 250- (void)setMeetOrSlice:(RNSVGVBMOS)meetOrSlice 251{ 252 if (meetOrSlice == _meetOrSlice) { 253 return; 254 } 255 [self invalidate]; 256 _meetOrSlice = meetOrSlice; 257} 258 259- (void)dealloc 260{ 261 CGImageRelease(_image); 262} 263 264- (void)renderLayerTo:(CGContextRef)context rect:(CGRect)rect 265{ 266 if (CGSizeEqualToSize(CGSizeZero, _imageSize)) { 267 return; 268 } 269 CGContextSaveGState(context); 270 271 // add hit area 272 CGRect hitArea = [self getHitArea]; 273 CGPathRef hitAreaPath = CGPathCreateWithRect(hitArea, nil); 274 [self setHitArea:hitAreaPath]; 275 CGPathRelease(hitAreaPath); 276 self.pathBounds = hitArea; 277 self.fillBounds = hitArea; 278 self.strokeBounds = hitArea; 279 280 // apply viewBox transform on Image render. 281 CGRect imageBounds = CGRectMake(0, 0, _imageSize.width, _imageSize.height); 282 CGAffineTransform viewbox = [RNSVGViewBox getTransform:imageBounds 283 eRect:hitArea 284 align:self.align 285 meetOrSlice:self.meetOrSlice]; 286 287 [self clip:context]; 288 CGContextClipToRect(context, hitArea); 289 CGContextConcatCTM(context, viewbox); 290 CGContextTranslateCTM(context, 0, imageBounds.size.height); 291 CGContextScaleCTM(context, 1, -1); 292 CGContextDrawImage(context, imageBounds, _image); 293 CGContextRestoreGState(context); 294 295 CGRect bounds = hitArea; 296 self.clientRect = bounds; 297 298 CGAffineTransform current = CGContextGetCTM(context); 299 CGAffineTransform svgToClientTransform = CGAffineTransformConcat(current, self.svgView.invInitialCTM); 300 301 self.ctm = svgToClientTransform; 302 self.screenCTM = current; 303 304 CGAffineTransform transform = CGAffineTransformConcat(self.matrix, self.transforms); 305 CGPoint mid = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds)); 306 CGPoint center = CGPointApplyAffineTransform(mid, transform); 307 308 self.bounds = bounds; 309 if (!isnan(center.x) && !isnan(center.y)) { 310 self.center = center; 311 } 312 self.frame = bounds; 313} 314 315- (CGRect)getHitArea 316{ 317 CGFloat x = [self relativeOnWidth:self.x]; 318 CGFloat y = [self relativeOnHeight:self.y]; 319 CGFloat width = [self relativeOnWidth:self.imagewidth]; 320 CGFloat height = [self relativeOnHeight:self.imageheight]; 321 if (width == 0) { 322 width = _imageSize.width; 323 } 324 if (height == 0) { 325 height = _imageSize.height; 326 } 327 328 return CGRectMake(x, y, width, height); 329} 330 331- (CGPathRef)getPath:(CGContextRef)context 332{ 333 return (CGPathRef)CFAutorelease(CGPathCreateWithRect([self getHitArea], nil)); 334} 335 336@end 337 338#ifdef RCT_NEW_ARCH_ENABLED 339Class<RCTComponentViewProtocol> RNSVGImageCls(void) 340{ 341 return RNSVGImage.class; 342} 343#endif // RCT_NEW_ARCH_ENABLED 344