带有系统映像的倒置掩码 swiftui
Inverted mask swiftui with system image
我想在 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 设置为红色。
有什么方法可以反转 symbol/image 的蒙版,使圆圈中有一个洞,形状与图像相同?
您可以使用其他图片:
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)
)
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()
)
受到this post的启发,这是我的解决方案:
首先,让我们做一个视图修饰符.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())
结果是:
对于剪切,只需使用 .blendMode(.destinationOut)
和 .compositingGroup()
。无需重复形状、屏蔽它并定义 white/black.
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()
}
}
我想在 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 设置为红色。
有什么方法可以反转 symbol/image 的蒙版,使圆圈中有一个洞,形状与图像相同?
您可以使用其他图片:
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)
)
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()
)
受到this post的启发,这是我的解决方案:
首先,让我们做一个视图修饰符.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())
结果是:
对于剪切,只需使用 .blendMode(.destinationOut)
和 .compositingGroup()
。无需重复形状、屏蔽它并定义 white/black.
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()
}
}