feat: implement mask-type property (#2152)

# Summary

"mask-type: alpha" is not supported. 
resolve issue: #1790  

## Explanation

svg example:
```
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100" fill="none">
<g clip-path="url(#clip0_8_3)">
<rect width="100" height="100" fill="white"/>
<mask id="mask0_8_3" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
<circle cx="50" cy="50" r="50" fill="#000000"/>
</mask>
<g mask="url(#mask0_8_3)">
<rect x="-26" y="-78" width="209" height="263" fill="#252E74"/>
</g>
</g>
<defs>
<clipPath id="clip0_8_3">
<rect width="100" height="100" fill="white"/>
</clipPath>
</defs>
</svg>
```

Current behavior: 

![image](https://github.com/software-mansion/react-native-svg/assets/17138397/2dca6f46-fe8f-48f3-80f9-799563911e8b)

Expected behavior:

![image](https://github.com/software-mansion/react-native-svg/assets/17138397/fb49cf0b-d677-491f-8215-9c9b1d69080f)

## Compatibility

| OS      | Implemented |
| ------- | :---------: |
| iOS     |        |
| Android |        |

## Checklist

<!-- Check completed item, when applicable, via: [X] -->

- [x] I have tested this on a device and a simulator
- [ ] I added documentation in `README.md`
- [x] I updated the typed files (typescript)
- [ ] I added a test for the API in the `__tests__` folder

---------

Co-authored-by: Sergey <s.yurkevich@logiclike.com>
Co-authored-by: Jakub Grzywacz <jakub.grzywacz@swmansion.com>
This commit is contained in:
SergeyYurkevich
2024-07-05 16:11:48 +03:00
committed by GitHub
parent 7c1602e291
commit 832522d1c1
19 changed files with 166 additions and 40 deletions
+3 -2
View File
@@ -237,10 +237,11 @@ void PatternFunction(void *info, CGContextRef context)
rx = width;
ry = height;
CGGradientRelease(gradient);
NSArray<NSNumber *> *gradientArray = @[_colors.firstObject, _colors.lastObject, _colors[_colors.count-2], _colors.lastObject];
NSArray<NSNumber *> *gradientArray =
@[ _colors.firstObject, _colors.lastObject, _colors[_colors.count - 2], _colors.lastObject ];
gradient = CGGradientRetain([RCTConvert RNSVGCGGradient:gradientArray]);
}
double ratio = ry / rx;
CGFloat fx = [self getVal:[_points objectAtIndex:0] relative:width] + offsetX;
+17 -14
View File
@@ -136,10 +136,13 @@ using namespace facebook::react;
auto imageSource = _state->getData().getImageSource();
imageSource.size = {image.size.width, image.size.height};
if (_eventEmitter != nullptr) {
static_cast<const RNSVGImageEventEmitter &>(*_eventEmitter).onLoad({.source = {
.width = imageSource.size.width * imageSource.scale,
.height = imageSource.size.height * imageSource.scale,
.uri = imageSource.uri, }});
static_cast<const RNSVGImageEventEmitter &>(*_eventEmitter)
.onLoad(
{.source = {
.width = imageSource.size.width * imageSource.scale,
.height = imageSource.size.height * imageSource.scale,
.uri = imageSource.uri,
}});
}
dispatch_async(dispatch_get_main_queue(), ^{
self->_image = CGImageRetain(image.CGImage);
@@ -210,20 +213,20 @@ using namespace facebook::react;
dispatch_async(dispatch_get_main_queue(), ^{
self->_image = CGImageRetain(image.CGImage);
self->_imageSize = CGSizeMake(CGImageGetWidth(self->_image), CGImageGetHeight(self->_image));
if (self->_onLoad) {
RCTImageSource *sourceLoaded;
if (self->_onLoad) {
RCTImageSource *sourceLoaded;
#if TARGET_OS_OSX // [macOS]
sourceLoaded = [src imageSourceWithSize:image.size scale:1];
sourceLoaded = [src imageSourceWithSize:image.size scale:1];
#else
sourceLoaded = [src imageSourceWithSize:image.size scale:image.scale];
#endif
NSDictionary *dict = @{
@"uri" : sourceLoaded.request.URL.absoluteString,
@"width" : @(sourceLoaded.size.width),
@"height" : @(sourceLoaded.size.height),
};
self->_onLoad(@{@"source" : dict});
}
NSDictionary *dict = @{
@"uri" : sourceLoaded.request.URL.absoluteString,
@"width" : @(sourceLoaded.size.width),
@"height" : @(sourceLoaded.size.height),
};
self->_onLoad(@{@"source" : dict});
}
[self invalidate];
});
}];
+1
View File
@@ -10,5 +10,6 @@
@property (nonatomic, strong) RNSVGLength *maskheight;
@property (nonatomic, assign) RNSVGUnits maskUnits;
@property (nonatomic, assign) RNSVGUnits maskContentUnits;
@property (nonatomic, assign) RNSVGMaskType maskType;
@end
+11
View File
@@ -62,6 +62,7 @@ using namespace facebook::react;
self.maskUnits = newProps.maskUnits == 0 ? kRNSVGUnitsObjectBoundingBox : kRNSVGUnitsUserSpaceOnUse;
self.maskContentUnits = newProps.maskUnits == 0 ? kRNSVGUnitsObjectBoundingBox : kRNSVGUnitsUserSpaceOnUse;
self.maskType = newProps.maskType == 0 ? kRNSVGMaskTypeLuminance : kRNSVGMaskTypeAlpha;
setCommonGroupProps(newProps, self);
_props = std::static_pointer_cast<RNSVGMaskProps const>(props);
@@ -76,6 +77,7 @@ using namespace facebook::react;
_maskwidth = nil;
_maskUnits = kRNSVGUnitsObjectBoundingBox;
_maskContentUnits = kRNSVGUnitsObjectBoundingBox;
_maskType = kRNSVGMaskTypeLuminance;
}
#endif // RCT_NEW_ARCH_ENABLED
@@ -150,6 +152,15 @@ using namespace facebook::react;
[self invalidate];
}
- (void)setMaskType:(RNSVGMaskType)maskType
{
if (maskType == _maskType) {
return;
}
_maskType = maskType;
[self invalidate];
}
@end
#ifdef RCT_NEW_ARCH_ENABLED
+12 -10
View File
@@ -294,16 +294,18 @@ UInt32 saturate(CGFloat value)
// Apply luminanceToAlpha filter primitive
// https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
UInt32 *currentPixel = pixels;
for (NSUInteger i = 0; i < npixels; i++) {
UInt32 color = *currentPixel;
if (_maskNode.maskType == kRNSVGMaskTypeLuminance) {
for (NSUInteger i = 0; i < npixels; i++) {
UInt32 color = *currentPixel;
UInt32 r = color & 0xFF;
UInt32 g = (color >> 8) & 0xFF;
UInt32 b = (color >> 16) & 0xFF;
UInt32 r = color & 0xFF;
UInt32 g = (color >> 8) & 0xFF;
UInt32 b = (color >> 16) & 0xFF;
CGFloat luma = (CGFloat)(0.299 * r + 0.587 * g + 0.144 * b);
*currentPixel = saturate(luma) << 24;
currentPixel++;
CGFloat luma = (CGFloat)(0.299 * r + 0.587 * g + 0.144 * b);
*currentPixel = saturate(luma) << 24;
currentPixel++;
}
}
// Create mask image and release memory
@@ -361,11 +363,11 @@ UInt32 saturate(CGFloat value)
CGContextScaleCTM(newContext, 1.0, -1.0);
CGContextSetBlendMode(newContext, kCGBlendModeCopy);
CGContextDrawImage(newContext, maskBounds, maskImage);
CGContextDrawImage(newContext, maskBounds, maskImage);
CGImageRelease(maskImage);
CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
CGContextDrawImage(newContext, maskBounds, contentImage);
CGContextDrawImage(newContext, maskBounds, contentImage);
CGImageRelease(contentImage);
CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
+2
View File
@@ -12,6 +12,7 @@
#import "RCTConvert+RNSVG.h"
#import "RNSVGCGFCRule.h"
#import "RNSVGLength.h"
#import "RNSVGMaskType.h"
#import "RNSVGPathParser.h"
#import "RNSVGUnits.h"
#import "RNSVGVBMOS.h"
@@ -25,6 +26,7 @@
+ (RNSVGCGFCRule)RNSVGCGFCRule:(id)json;
+ (RNSVGVBMOS)RNSVGVBMOS:(id)json;
+ (RNSVGUnits)RNSVGUnits:(id)json;
+ (RNSVGMaskType)RNSVGMaskType:(id)json;
+ (RNSVGBrush *)RNSVGBrush:(id)json;
+ (RNSVGPathParser *)RNSVGCGPath:(NSString *)d;
+ (CGRect)RNSVGCGRect:(id)json offset:(NSUInteger)offset;
+9
View File
@@ -42,6 +42,15 @@ RCT_ENUM_CONVERTER(
kRNSVGUnitsObjectBoundingBox,
intValue)
RCT_ENUM_CONVERTER(
RNSVGMaskType,
(@{
@"luminance" : @(kRNSVGMaskTypeLuminance),
@"alpha" : @(kRNSVGMaskTypeAlpha),
}),
kRNSVGMaskTypeLuminance,
intValue)
+ (RNSVGBrush *)RNSVGBrush:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
+4
View File
@@ -0,0 +1,4 @@
typedef CF_ENUM(int32_t, RNSVGMaskType) {
kRNSVGMaskTypeLuminance,
kRNSVGMaskTypeAlpha
};
+1
View File
@@ -30,5 +30,6 @@ RCT_CUSTOM_VIEW_PROPERTY(width, id, RNSVGMask)
}
RCT_EXPORT_VIEW_PROPERTY(maskUnits, RNSVGUnits)
RCT_EXPORT_VIEW_PROPERTY(maskContentUnits, RNSVGUnits)
RCT_EXPORT_VIEW_PROPERTY(maskType, RNSVGMaskType)
@end