mirror of
https://github.com/zoriya/react-native-svg.git
synced 2026-06-07 08:45:00 +00:00
feat: implement maskUnits (#2457)
# Summary Without the `maskUnits` attribute, masks may not render correctly, as seen in issue #2449. This pull request adds support for `maskUnits` and ensures proper cropping within the mask boundaries. ## Compatibility | OS | Implemented | | ------- | :---------: | | iOS | ✅ | | MacOS | ✅ | | Android | ✅ |
This commit is contained in:
@@ -58,6 +58,10 @@ class MaskView extends GroupView {
|
|||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Brush.BrushUnits getMaskUnits() {
|
||||||
|
return mMaskUnits;
|
||||||
|
}
|
||||||
|
|
||||||
public void setMaskUnits(int maskUnits) {
|
public void setMaskUnits(int maskUnits) {
|
||||||
switch (maskUnits) {
|
switch (maskUnits) {
|
||||||
case 0:
|
case 0:
|
||||||
|
|||||||
@@ -411,12 +411,27 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calculate mask bounds
|
// calculate mask bounds
|
||||||
float maskX = (float) relativeOnWidth(mask.mX);
|
RectF maskBounds;
|
||||||
float maskY = (float) relativeOnHeight(mask.mY);
|
if (mask.getMaskUnits() == Brush.BrushUnits.USER_SPACE_ON_USE) {
|
||||||
float maskWidth = (float) relativeOnWidth(mask.mW);
|
float maskX = (float) relativeOnWidth(mask.mX);
|
||||||
float maskHeight = (float) relativeOnHeight(mask.mH);
|
float maskY = (float) relativeOnHeight(mask.mY);
|
||||||
|
float maskWidth = (float) relativeOnWidth(mask.mW);
|
||||||
|
float maskHeight = (float) relativeOnHeight(mask.mH);
|
||||||
|
maskBounds = new RectF(maskX, maskY, maskX + maskWidth, maskY + maskHeight);
|
||||||
|
} else { // Brush.BrushUnits.OBJECT_BOUNDING_BOX
|
||||||
|
RectF clientRect = this.getClientRect();
|
||||||
|
if (this instanceof ImageView && clientRect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInvCTM.mapRect(clientRect);
|
||||||
|
float maskX = (float) relativeOnFraction(mask.mX, clientRect.width());
|
||||||
|
float maskY = (float) relativeOnFraction(mask.mY, clientRect.height());
|
||||||
|
float maskWidth = (float) relativeOnFraction(mask.mW, clientRect.width());
|
||||||
|
float maskHeight = (float) relativeOnFraction(mask.mH, clientRect.height());
|
||||||
|
maskBounds = new RectF(clientRect.left + maskX, clientRect.top + maskY, clientRect.left + maskX + maskWidth, clientRect.top + maskY + maskHeight);
|
||||||
|
}
|
||||||
// clip to mask bounds
|
// clip to mask bounds
|
||||||
canvas.clipRect(maskX, maskY, maskX + maskWidth, maskY + maskHeight);
|
canvas.clipRect(maskBounds);
|
||||||
|
|
||||||
mask.draw(canvas, paint, 1f);
|
mask.draw(canvas, paint, 1f);
|
||||||
|
|
||||||
@@ -426,7 +441,7 @@ public abstract class RenderableView extends VirtualView implements ReactHitSlop
|
|||||||
// step 2 - alpha layer
|
// step 2 - alpha layer
|
||||||
canvas.saveLayer(null, dstInPaint);
|
canvas.saveLayer(null, dstInPaint);
|
||||||
// clip to mask bounds
|
// clip to mask bounds
|
||||||
canvas.clipRect(maskX, maskY, maskX + maskWidth, maskY + maskHeight);
|
canvas.clipRect(maskBounds);
|
||||||
|
|
||||||
mask.draw(canvas, paint, 1f);
|
mask.draw(canvas, paint, 1f);
|
||||||
|
|
||||||
|
|||||||
@@ -117,12 +117,12 @@ using namespace facebook::react;
|
|||||||
CGAffineTransform contextTransform = CGAffineTransformConcat(ctm, CGAffineTransformMakeTranslation(-ctm.tx, -ctm.ty));
|
CGAffineTransform contextTransform = CGAffineTransformConcat(ctm, CGAffineTransformMakeTranslation(-ctm.tx, -ctm.ty));
|
||||||
#if !TARGET_OS_OSX // [macOS]
|
#if !TARGET_OS_OSX // [macOS]
|
||||||
CGPoint translate = CGPointMake(dx, dy);
|
CGPoint translate = CGPointMake(dx, dy);
|
||||||
#else
|
#else // [macOS
|
||||||
CGPoint translate = CGPointMake(dx, -dy);
|
CGPoint translate = CGPointMake(dx, dy);
|
||||||
CGFloat scale = [RNSVGRenderUtils getScreenScale];
|
CGFloat scale = [RNSVGRenderUtils getScreenScale];
|
||||||
CGAffineTransform screenScaleCTM = CGAffineTransformMake(scale, 0, 0, scale, 0, 0);
|
CGAffineTransform screenScaleCTM = CGAffineTransformMake(scale, 0, 0, scale, 0, 0);
|
||||||
translate = CGPointApplyAffineTransform(translate, screenScaleCTM);
|
translate = CGPointApplyAffineTransform(translate, screenScaleCTM);
|
||||||
#endif
|
#endif // macOS]
|
||||||
translate = CGPointApplyAffineTransform(translate, contextTransform);
|
translate = CGPointApplyAffineTransform(translate, contextTransform);
|
||||||
CGAffineTransform transform = CGAffineTransformMakeTranslation(translate.x, translate.y);
|
CGAffineTransform transform = CGAffineTransformMakeTranslation(translate.x, translate.y);
|
||||||
|
|
||||||
|
|||||||
+21
-18
@@ -270,7 +270,11 @@ UInt32 saturate(CGFloat value)
|
|||||||
CGAffineTransform screenScaleCTM = CGAffineTransformMake(scale, 0, 0, scale, 0, 0);
|
CGAffineTransform screenScaleCTM = CGAffineTransformMake(scale, 0, 0, scale, 0, 0);
|
||||||
CGRect scaledRect = CGRectApplyAffineTransform(rect, screenScaleCTM);
|
CGRect scaledRect = CGRectApplyAffineTransform(rect, screenScaleCTM);
|
||||||
|
|
||||||
|
#if TARGET_OS_OSX
|
||||||
|
CGImage *contentImage = [RNSVGRenderUtils renderToImage:self ctm:currentCTM rect:scaledRect clip:nil];
|
||||||
|
#else
|
||||||
CGImage *contentImage = [RNSVGRenderUtils renderToImage:self ctm:currentCTM rect:rect clip:nil];
|
CGImage *contentImage = [RNSVGRenderUtils renderToImage:self ctm:currentCTM rect:rect clip:nil];
|
||||||
|
#endif
|
||||||
|
|
||||||
if (filterNode) {
|
if (filterNode) {
|
||||||
// https://www.w3.org/TR/SVG11/filters.html#FilterElement
|
// https://www.w3.org/TR/SVG11/filters.html#FilterElement
|
||||||
@@ -292,12 +296,7 @@ UInt32 saturate(CGFloat value)
|
|||||||
|
|
||||||
if (!maskNode) {
|
if (!maskNode) {
|
||||||
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
|
CGContextConcatCTM(context, CGAffineTransformInvert(currentCTM));
|
||||||
|
|
||||||
// On macOS the currentCTM is inverted, so we need to transform it again
|
|
||||||
// https://stackoverflow.com/a/13358085
|
|
||||||
#if TARGET_OS_OSX
|
#if TARGET_OS_OSX
|
||||||
CGContextTranslateCTM(context, 0.0, rect.size.height);
|
|
||||||
CGContextScaleCTM(context, 1.0, -1.0);
|
|
||||||
CGContextDrawImage(context, rect, contentImage);
|
CGContextDrawImage(context, rect, contentImage);
|
||||||
#else
|
#else
|
||||||
CGContextDrawImage(context, scaledRect, contentImage);
|
CGContextDrawImage(context, scaledRect, contentImage);
|
||||||
@@ -328,18 +327,24 @@ UInt32 saturate(CGFloat value)
|
|||||||
#if TARGET_OS_OSX // [macOS]
|
#if TARGET_OS_OSX // [macOS]
|
||||||
// on macOS currentCTM is not scaled properly with screen scale so we need to scale it manually
|
// on macOS currentCTM is not scaled properly with screen scale so we need to scale it manually
|
||||||
CGContextConcatCTM(bcontext, screenScaleCTM);
|
CGContextConcatCTM(bcontext, screenScaleCTM);
|
||||||
CGContextTranslateCTM(bcontext, 0, rect.size.height);
|
|
||||||
CGContextScaleCTM(bcontext, 1, -1);
|
|
||||||
|
|
||||||
#endif // [macOS]
|
#endif // [macOS]
|
||||||
CGContextConcatCTM(bcontext, currentCTM);
|
CGContextConcatCTM(bcontext, currentCTM);
|
||||||
// Clip to mask bounds and render the mask
|
// Clip to mask bounds and render the mask
|
||||||
CGSize currentBoundsSize = self.pathBounds.size;
|
CGRect maskBounds;
|
||||||
CGFloat x = [self relativeOnFraction:[maskNode x] relative:currentBoundsSize.width];
|
if ([maskNode maskUnits] == RNSVGUnits::kRNSVGUnitsUserSpaceOnUse) {
|
||||||
CGFloat y = [self relativeOnFraction:[maskNode y] relative:currentBoundsSize.height];
|
CGFloat x = [self relativeOn:[maskNode x] relative:width];
|
||||||
CGFloat w = [self relativeOnFraction:[maskNode maskwidth] relative:currentBoundsSize.width];
|
CGFloat y = [self relativeOn:[maskNode y] relative:height];
|
||||||
CGFloat h = [self relativeOnFraction:[maskNode maskheight] relative:currentBoundsSize.height];
|
CGFloat w = [self relativeOn:[maskNode maskwidth] relative:width];
|
||||||
CGRect maskBounds = CGRectApplyAffineTransform(CGRectMake(x, y, w, h), screenScaleCTM);
|
CGFloat h = [self relativeOn:[maskNode maskheight] relative:height];
|
||||||
|
maskBounds = CGRectMake(x, y, w, h);
|
||||||
|
} else {
|
||||||
|
CGSize currentBoundsSize = self.pathBounds.size;
|
||||||
|
CGFloat x = [self relativeOnFraction:[maskNode x] relative:currentBoundsSize.width];
|
||||||
|
CGFloat y = [self relativeOnFraction:[maskNode y] relative:currentBoundsSize.height];
|
||||||
|
CGFloat w = [self relativeOnFraction:[maskNode maskwidth] relative:currentBoundsSize.width];
|
||||||
|
CGFloat h = [self relativeOnFraction:[maskNode maskheight] relative:currentBoundsSize.height];
|
||||||
|
maskBounds = CGRectMake(self.pathBounds.origin.x + x, self.pathBounds.origin.y + y, w, h);
|
||||||
|
}
|
||||||
CGContextClipToRect(bcontext, maskBounds);
|
CGContextClipToRect(bcontext, maskBounds);
|
||||||
[maskNode renderLayerTo:bcontext rect:bounds];
|
[maskNode renderLayerTo:bcontext rect:bounds];
|
||||||
|
|
||||||
@@ -394,12 +399,10 @@ UInt32 saturate(CGFloat value)
|
|||||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
|
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
|
||||||
CGContextRef newContext = UIGraphicsGetCurrentContext();
|
CGContextRef newContext = UIGraphicsGetCurrentContext();
|
||||||
|
|
||||||
CGContextConcatCTM(newContext, CGAffineTransformInvert(CGContextGetCTM(newContext)));
|
|
||||||
|
|
||||||
CGContextSetBlendMode(newContext, kCGBlendModeCopy);
|
CGContextSetBlendMode(newContext, kCGBlendModeCopy);
|
||||||
CGContextDrawImage(newContext, scaledRect, maskImage);
|
CGContextDrawImage(newContext, rect, maskImage);
|
||||||
CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
|
CGContextSetBlendMode(newContext, kCGBlendModeSourceIn);
|
||||||
CGContextDrawImage(newContext, scaledRect, contentImage);
|
CGContextDrawImage(newContext, rect, contentImage);
|
||||||
|
|
||||||
CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
|
CGImageRef blendedImage = CGBitmapContextCreateImage(newContext);
|
||||||
UIGraphicsEndImageContext();
|
UIGraphicsEndImageContext();
|
||||||
|
|||||||
@@ -36,11 +36,16 @@
|
|||||||
clip:(CGRect *)clip
|
clip:(CGRect *)clip
|
||||||
{
|
{
|
||||||
CGFloat scale = [self getScreenScale];
|
CGFloat scale = [self getScreenScale];
|
||||||
|
#if TARGET_OS_OSX // [macOS
|
||||||
|
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 1.0);
|
||||||
|
#else // macOS]
|
||||||
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
|
UIGraphicsBeginImageContextWithOptions(rect.size, NO, scale);
|
||||||
|
#endif // [macOS]
|
||||||
CGContextRef cgContext = UIGraphicsGetCurrentContext();
|
CGContextRef cgContext = UIGraphicsGetCurrentContext();
|
||||||
#if !TARGET_OS_OSX
|
|
||||||
CGContextConcatCTM(cgContext, CGAffineTransformInvert(CGContextGetCTM(cgContext)));
|
CGContextConcatCTM(cgContext, CGAffineTransformInvert(CGContextGetCTM(cgContext)));
|
||||||
#endif
|
#if TARGET_OS_OSX // [macOS
|
||||||
|
CGContextConcatCTM(cgContext, CGAffineTransformMakeScale(scale, scale));
|
||||||
|
#endif // macOS]
|
||||||
CGContextConcatCTM(cgContext, ctm);
|
CGContextConcatCTM(cgContext, ctm);
|
||||||
|
|
||||||
if (clip) {
|
if (clip) {
|
||||||
|
|||||||
Reference in New Issue
Block a user