9

我正在尝试从 swiftUI 中的圆形中剪下一个 sf 符号。

我目前有以下代码:

Circle()
   .fill(Color.white)
   .frame(width: 50, height: 50)
   .mask(
       Image(systemName: "play.fill")
           .font(.system(size: 24))
           .foregroundColor(Color.black)
           .frame(width: 50, height: 50)
   )

生成:

在此处输入图像描述

但是,我想要的是反转蒙版的效果:该符号从圆圈中切出,如下图所示:

在此处输入图像描述

请注意,实际背景不是图像中的红色,而是用户上传的图像,因此无法将符号 foregroundColor 设置为红色。

有什么方法可以反转符号/图像的蒙版,使圆圈中有一个带有图像形状的孔?

4

4 回答 4

10

对于抠图,只需使用.blendMode(.destinationOut).compositingGroup()。无需重复形状、遮盖它并定义白色/黑色。

var buttonCutOut: some View {
        ZStack {
            shape
            mask.blendMode(.destinationOut)
        }.compositingGroup()
    }

这是一个用于蒙版形状或亮度蒙版形状的示例可重用组件。

在此处输入图像描述


struct Demo: View {
    var body: some View {
        ZStack {
            userImage.blur(radius: 5)
            playButtons
        }
    }

    var playButtons: some View {
        VStack {
            Mask(thisShape: Circle(),
                 with: maskImage,
                 offset: maskImageOffset,
                 maskToShapeRatio: maskImageToShapeRatio)
                .frame(width: square, height: square)
                .foregroundColor(colorDestinationOut)

            LuminanceMask(thisShape: Circle(),
                          with: maskImage,
                          offset: maskImageOffset,
                          maskToShapeRatio: maskImageToShapeRatio)
                .frame(width: square, height: square)
                .foregroundColor(colorLuminanceAlpha)
        }
        .font(font)
    }
}

struct Mask<S: Shape>: View {
    init(thisShape: S,
         with mask: Image,
         offset: CGSize,
         maskToShapeRatio: CGFloat) {
        self.shape = thisShape
        self.mask = mask
        self.offset = offset
        self.scale = maskToShapeRatio
    }

    let shape: S
    let mask: Image
    let offset: CGSize
    let scale: CGFloat

    var body: some View {
        ZStack(alignment: .center) {
            shape.fill()
            mask
                .resizable()
                .offset(offset)
                .aspectRatio(contentMode: .fit)
                .blendMode(.destinationOut)
                .scaleEffect(scale)
        }.compositingGroup()
    }
}

struct LuminanceMask<S: Shape>: View {
    init(thisShape: S,
         with mask: Image,
         offset: CGSize,
         maskToShapeRatio: CGFloat) {
        self.shape = thisShape
        self.mask = mask
        self.offset = offset
        self.scale = maskToShapeRatio
    }

    let shape: S
    let mask: Image
    let offset: CGSize
    let scale: CGFloat

    let keep = Color.white
    let remove = Color.black

    var body: some View {
            shape.mask(maskView)
    }

    var maskView: some View {
        ZStack(alignment: .center) {
            shape
                .foregroundColor(keep)

            mask
                .resizable()
                .offset(offset)
                .aspectRatio(contentMode: .fit)
                .scaleEffect(scale)
                .foregroundColor(remove)

        }.compositingGroup()
        .luminanceToAlpha()

    }
}

于 2020-10-05T12:57:26.777 回答
6

这篇文章的启发,这是我的解决方案:

首先,让我们制作一个视图修饰符.inverseMask()

extension View {
    // view.inverseMask(_:)
    public func inverseMask<M: View>(_ mask: M) -> some View {
        // exchange foreground and background
        let inversed = mask
            .foregroundColor(.black)  // hide foreground
            .background(Color.white)  // let the background stand out
            .compositingGroup()       // ⭐️ composite all layers
            .luminanceToAlpha()       // ⭐️ turn luminance into alpha (opacity)
        return self.mask(inversed)
    }
}

其次,我们做一个演示视图:

请注意,我在下面的代码中使用了自定义扩展LinearGradient,它列在底部。

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        
        // lightbulb image
        let lightbulb = Image(systemName: "lightbulb")
            .resizable().scaledToFit().padding(24)
        // rounded rect (shape)
        let roundedRect = RoundedRectangle(cornerRadius: 20)
        // rounded rect (stroked border)
        let border = roundedRect
            .stroke(
                Gradient.diagonal(.white, .black),  // my custom extension
                lineWidth: 2
        )
        
        // container
        return ZStack {
            // background color
            Color.white.grayscale(0.3)
                
            // rounded card
            Gradient.horizontal(.gray, .black)      // my custom extension 
                
                // ⭐️ inverse mask
                .inverseMask(lightbulb)
                
                .shadow(color: Color.black.opacity(0.6), radius: 4, x: 4, y: 4)
                .frame(width: 150, height: 200)
                .clipShape(roundedRect)
                .overlay(border)
                .shadow(color: Color.white.opacity(0.9), radius: 18, x: -18, y: -18)
                .shadow(color: Color.black.opacity(0.3), radius: 14, x: 14, y: 14)
            
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView())

结果是:

在此处输入图像描述

----[已编辑]----

Gradient如果您好奇,这是我的自定义扩展:

import SwiftUI

extension Gradient {
    
    // general linear gradient ---------------------------
    
    public static func linear(
        from start: UnitPoint, 
        to     end: UnitPoint, 
        colors    : [Color]       // use array
    ) -> LinearGradient 
    {
        LinearGradient(
            gradient  : Gradient(colors: colors), 
            startPoint: start, 
            endPoint  : end
        )
    }
    
    public static func linear(
        from start: UnitPoint, 
        to     end: UnitPoint, 
        colors    : Color...     // use variadic parameter
    ) -> LinearGradient 
    {
        linear(from: start, to: end, colors: colors)
    }
    
    // specialized linear gradients ------------------------
    
    // top to bottom
    public static func vertical(_ colors: Color...) -> LinearGradient {
        linear(from: .top, to: .bottom, colors: colors)
    }
    
    // leading to trailing
    public static func horizontal(_ colors: Color...) -> LinearGradient {
        linear(from: .leading, to: .trailing, colors: colors)
    }
    
    // top leading to bottom trailing
    public static func diagonal(_ colors: Color...) -> LinearGradient {
        linear(from: .topLeading, to: .bottomTrailing, colors: colors)
    }
    
    // top leading to bottom trailing
    public static func diagonal2(_ colors: Color...) -> LinearGradient {
        linear(from: .bottomLeading, to: .topTrailing, colors: colors)
    }
}

哦,顺便说一句,也许这就是 OP 想要的:

import SwiftUI
import PlaygroundSupport

struct ContentView: View {
    var body: some View {
        
        // background image
        let background = Image("red stars.PNG")
        
        // mask
        let mask = Image(systemName: "play.circle.fill")
            .font(.system(size: 100))
            .scaledToFit()               // center the mask
        
        // container
        return ZStack {
            
            // background color
            Color.white.grayscale(0.3)
            
            // card
            background
                .inverseMask(mask)                    // ⭐️ cut out the mask
                // shadow for hole
                .shadow(color: Color.black.opacity(0.7), radius: 3, x: 3, y: 3)
                // highlight & shadow for border
                .shadow(color: Color.white.opacity(0.9), radius: 18, x: -18, y: -18)
                .shadow(color: Color.black.opacity(0.3), radius: 14, x:  14, y:  14)
        }
    }
}

PlaygroundPage.current.setLiveView(ContentView())

结果是:

在此处输入图像描述

于 2020-09-17T08:32:13.773 回答
3

Vlad Lego 的这个答案在这里应用时有效。

.black您可以为要剪切.white的部分和要保留的部分创建蒙版。

(假设背景是红色的,这就是它的样子)

在此处输入图像描述

Rectangle()
  .foregroundColor(Color.white)
  .mask(
    ZStack {
      Circle()
        .fill(Color.white)
        .frame(width: 50, height: 50)
      Image(systemName: "play.fill")
        .font(.system(size: 24))
        .foregroundColor(Color.black)
    }
      .compositingGroup()
      .luminanceToAlpha()
  )
于 2020-06-02T22:46:14.633 回答
0

您可以使用另一张图片:

 Circle()
    .fill(Color.white)
    .frame(width: 50, height: 50)
    .mask(
        Image(systemName: "play.circle.fill")
            .font(.system(size: 24))
            .foregroundColor(Color.black)
            .frame(width: 50, height: 50)
    )
于 2020-02-12T16:25:59.677 回答