mirror of
https://github.com/zoriya/react-native-svg.git
synced 2025-12-06 07:06:11 +00:00
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:  Expected behavior:  ## 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:
@@ -27,6 +27,13 @@ class MaskView extends GroupView {
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private Brush.BrushUnits mMaskContentUnits;
|
||||
|
||||
MaskType mMaskType;
|
||||
|
||||
enum MaskType {
|
||||
LUMINANCE,
|
||||
ALPHA
|
||||
}
|
||||
|
||||
public MaskView(ReactContext reactContext) {
|
||||
super(reactContext);
|
||||
}
|
||||
@@ -75,6 +82,22 @@ class MaskView extends GroupView {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public MaskType getMaskType() {
|
||||
return mMaskType;
|
||||
}
|
||||
|
||||
public void setMaskType(int maskType) {
|
||||
switch (maskType) {
|
||||
case 0:
|
||||
mMaskType = MaskType.LUMINANCE;
|
||||
break;
|
||||
case 1:
|
||||
mMaskType = MaskType.ALPHA;
|
||||
break;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
void saveDefinition() {
|
||||
if (mName != null) {
|
||||
|
||||
@@ -350,17 +350,21 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
|
||||
// prepare step 3 - combined layer
|
||||
canvas.saveLayer(null, dstInPaint);
|
||||
|
||||
if (mask.getMaskType() == MaskView.MaskType.LUMINANCE) {
|
||||
// step 1 - luminance layer
|
||||
// prepare maskPaint with luminanceToAlpha
|
||||
// https://www.w3.org/TR/SVG11/filters.html#InterfaceSVGFEMergeElement:~:text=not%20applicable.%20A-,luminanceToAlpha,-operation%20is%20equivalent
|
||||
Paint luminancePaint = new Paint();
|
||||
ColorMatrix luminanceToAlpha =
|
||||
new ColorMatrix(
|
||||
new float[] {
|
||||
new float[]{
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.2125f, 0.7154f, 0.0721f, 0, 0
|
||||
});
|
||||
luminancePaint.setColorFilter(new ColorMatrixColorFilter(luminanceToAlpha));
|
||||
canvas.saveLayer(null, luminancePaint);
|
||||
} else {
|
||||
canvas.saveLayer(null, paint);
|
||||
}
|
||||
|
||||
// calculate mask bounds
|
||||
float maskX = (float) relativeOnWidth(mask.mX);
|
||||
|
||||
@@ -1275,6 +1275,11 @@ class RenderableViewManager<T extends RenderableView> extends VirtualViewManager
|
||||
public void setMaskContentUnits(MaskView node, int maskContentUnits) {
|
||||
node.setMaskContentUnits(maskContentUnits);
|
||||
}
|
||||
|
||||
@ReactProp(name = "maskType")
|
||||
public void setMaskType(MaskView node, int maskType) {
|
||||
node.setMaskType(maskType);
|
||||
}
|
||||
}
|
||||
|
||||
static class ForeignObjectManager extends GroupViewManagerAbstract<ForeignObjectView>
|
||||
|
||||
@@ -126,6 +126,9 @@ public class RNSVGMaskManagerDelegate<T extends View, U extends BaseViewManagerI
|
||||
case "maskContentUnits":
|
||||
mViewManager.setMaskContentUnits(view, value == null ? 0 : ((Double) value).intValue());
|
||||
break;
|
||||
case "maskType":
|
||||
mViewManager.setMaskType(view, value == null ? 0 : ((Double) value).intValue());
|
||||
break;
|
||||
default:
|
||||
super.setProperty(view, propName, value);
|
||||
}
|
||||
|
||||
@@ -50,4 +50,5 @@ public interface RNSVGMaskManagerInterface<T extends View> {
|
||||
void setWidth(T view, Dynamic value);
|
||||
void setMaskUnits(T view, int value);
|
||||
void setMaskContentUnits(T view, int value);
|
||||
void setMaskType(T view, int value);
|
||||
}
|
||||
|
||||
@@ -237,7 +237,8 @@ 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]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
static_cast<const RNSVGImageEventEmitter &>(*_eventEmitter)
|
||||
.onLoad(
|
||||
{.source = {
|
||||
.width = imageSource.size.width * imageSource.scale,
|
||||
.height = imageSource.size.height * imageSource.scale,
|
||||
.uri = imageSource.uri, }});
|
||||
.uri = imageSource.uri,
|
||||
}});
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self->_image = CGImageRetain(image.CGImage);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -294,6 +294,7 @@ UInt32 saturate(CGFloat value)
|
||||
// Apply luminanceToAlpha filter primitive
|
||||
// https://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement
|
||||
UInt32 *currentPixel = pixels;
|
||||
if (_maskNode.maskType == kRNSVGMaskTypeLuminance) {
|
||||
for (NSUInteger i = 0; i < npixels; i++) {
|
||||
UInt32 color = *currentPixel;
|
||||
|
||||
@@ -305,6 +306,7 @@ UInt32 saturate(CGFloat value)
|
||||
*currentPixel = saturate(luma) << 24;
|
||||
currentPixel++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create mask image and release memory
|
||||
CGImageRef maskImage = CGBitmapContextCreateImage(bcontext);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
apple/Utils/RNSVGMaskType.h
Normal file
4
apple/Utils/RNSVGMaskType.h
Normal file
@@ -0,0 +1,4 @@
|
||||
typedef CF_ENUM(int32_t, RNSVGMaskType) {
|
||||
kRNSVGMaskTypeLuminance,
|
||||
kRNSVGMaskTypeAlpha
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -8,6 +8,7 @@ import Test1374 from './src/Test1374';
|
||||
import Test1442 from './src/Test1442';
|
||||
import Test1451 from './src/Test1451';
|
||||
import Test1718 from './src/Test1718';
|
||||
import Test1790 from './src/Test1790';
|
||||
import Test1813 from './src/Test1813';
|
||||
import Test1845 from './src/Test1845';
|
||||
import Test1986 from './src/Test1986';
|
||||
@@ -23,5 +24,5 @@ import Test2276 from './src/Test2276';
|
||||
import Test2327 from './src/Test2327';
|
||||
|
||||
export default function App() {
|
||||
return <ColorTest />;
|
||||
return <Test1790 />;
|
||||
}
|
||||
|
||||
35
apps/test-examples/src/Test1790.tsx
Normal file
35
apps/test-examples/src/Test1790.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {SvgXml} from 'react-native-svg';
|
||||
|
||||
const svg = `<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" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
|
||||
<circle cx="50" cy="50" r="50" fill="#7e7e7e"/>
|
||||
</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>
|
||||
`;
|
||||
|
||||
export default function Test1790() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: 'red',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<SvgXml xml={svg} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -6,8 +6,10 @@ import units from '../lib/units';
|
||||
import Shape from './Shape';
|
||||
import RNSVGMask from '../fabric/MaskNativeComponent';
|
||||
import type { NativeMethods } from 'react-native';
|
||||
import { maskType } from '../lib/maskType';
|
||||
|
||||
export type TMaskUnits = 'userSpaceOnUse' | 'objectBoundingBox';
|
||||
export type TMaskType = 'alpha' | 'luminance';
|
||||
|
||||
export interface MaskProps extends CommonPathProps {
|
||||
children?: ReactNode;
|
||||
@@ -18,6 +20,10 @@ export interface MaskProps extends CommonPathProps {
|
||||
height?: NumberProp;
|
||||
maskUnits?: TMaskUnits;
|
||||
maskContentUnits?: TMaskUnits;
|
||||
maskType?: TMaskType;
|
||||
style?: {
|
||||
maskType: TMaskType;
|
||||
};
|
||||
}
|
||||
|
||||
export default class Mask extends Shape<MaskProps> {
|
||||
@@ -32,8 +38,16 @@ export default class Mask extends Shape<MaskProps> {
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const { x, y, width, height, maskUnits, maskContentUnits, children } =
|
||||
props;
|
||||
const {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
maskUnits,
|
||||
maskContentUnits,
|
||||
children,
|
||||
style,
|
||||
} = props;
|
||||
const maskProps = {
|
||||
x,
|
||||
y,
|
||||
@@ -42,6 +56,7 @@ export default class Mask extends Shape<MaskProps> {
|
||||
maskUnits: maskUnits !== undefined ? units[maskUnits] : 0,
|
||||
maskContentUnits:
|
||||
maskContentUnits !== undefined ? units[maskContentUnits] : 1,
|
||||
maskType: maskType[props?.maskType || style?.maskType || 'luminance'],
|
||||
};
|
||||
return (
|
||||
<RNSVGMask
|
||||
|
||||
@@ -64,6 +64,7 @@ interface NativeProps
|
||||
width?: UnsafeMixed<NumberProp>;
|
||||
maskUnits?: Int32;
|
||||
maskContentUnits?: Int32;
|
||||
maskType?: Int32;
|
||||
}
|
||||
|
||||
export default codegenNativeComponent<NativeProps>('RNSVGMask');
|
||||
|
||||
4
src/lib/maskType.ts
Normal file
4
src/lib/maskType.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const maskType = {
|
||||
luminance: 0,
|
||||
alpha: 1,
|
||||
} as const;
|
||||
Reference in New Issue
Block a user