Перевернутая маска swiftui с системным изображением
Я пытаюсь вырезать символ SF из формы круга в swiftUI.
В настоящее время у меня есть следующий код:
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 невозможна.
Есть ли способ инвертировать маску символа / изображения так, чтобы в круге было отверстие с формой изображения?
2 ответа
Этот ответ Влада Лего работает, когда применяется здесь.
Вы создаете свою маску, используя .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()
)
Для вырезов просто используйте
.blendMode(.destinationOut)
и
.compositingGroup()
. Не нужно повторять форму, маскировать ее и определять белый / черный цвет.
var buttonCutOut: some View {
ZStack {
shape
mask.blendMode(.destinationOut)
}.compositingGroup()
}
Вот пример многоразового компонента для маскированной формы или формы с маской яркости.
ht tps://stackru.com/images/882a052f5fa3c06b108a71110f7b10b257b62cc3.png
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()
}
}
Вот мое решение, вдохновленное этим сообщением:
Сначала сделаем модификатор вида
.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)
}
}
О, кстати, может быть, это то, чего хотел ОП:
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())
и результат:
Вы можете использовать другое изображение:
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)
)