在 SwiftUI 中将图像屏蔽到另一个图像的透明部分
Masking an image to the transparent section of another Image in SwiftUI
我有一个ZStack
这样的
ZStack {
Image("source")
Image("source")
}
其中一层是实心正方形图像,另一层是六边形图像,比它后面的图像略小;里面有一个六边形的透明区域,就像它后面的图像的框架。我正在尝试剪裁框架后面的图像,以便它仅显示在框架图像中心的透明区域中。
我尝试将帧图像声明为我在视图中使用的 var 并作为其背后图像的掩码,但这显然会将图像裁剪为帧的确切形状。我的想法是我可以反转我的框架图像的 alpha 通道以显示它后面的图片,它是透明的。但是我没有运气反转图像的 alpha 通道。
看起来是这样的:
ZStack {
let mImg = Image("source")
// img
Image("source")
.mask(mImg) //attempting to invert alpha channel of frame to be used here
// frame
mImg
}
在无法反转 alpha 通道之后,我还认为 blendMode
可能会起作用,将框架放在 img 后面,并将 blendMode 应用于 img,以便框架绘制在img 仅在框架透明的区域绘制,如下所示:
ZStack {
// frame
Image("source")
// img
Image("source")
.blenMode(.destinationOver)
}
这导致框架被绘制在顶部,但图像完全消失了。所以后来我读到 .destinationOut
它仅在顶部透明的地方显示底层。我认为:
ZStack {
// img
Image("source")
// frame
Image("source")
.blenMode(.destinationOver)
}
意味着只绘制底层 (img),但仅在顶层 (frame) 的区域是透明的。然后我可以在上面添加一个额外的框架层,因为它只会用于混合这些线条。然而,这种混合模式的行为与文档中所说的完全不同,实际上仍然绘制了两个图层..但是 img 图层顶部的框架是纯黑色..
不用说,我在这件事上浪费的时间比我愿意承认的还要多,我觉得我把一些简单的事情变得非常复杂,因为我遗漏了修改器顺序或混合或蒙版选项的一个小变化.
一如既往地非常感谢任何人可以就该主题提供的任何建议、信息或帮助。如果有人想尝试使用代码来解决问题,我还可以模拟一个简短的工作示例结构。感谢您随时为我提供先进的帮助。
示例图片:
更新:
所以我使用了这个 UIImage 扩展:
extension UIImage {
class func imageByCombiningImage(firstImageUrl: String, withImageUrl: String) -> UIImage {
let firstImage = try? UIImage(withContentsOfUrl: URL(string: firstImageUrl)!)
let secondImage = try? UIImage(withContentsOfUrl: URL(string: withImageUrl)!)
let newImageWidth = max(firstImage!.size.width, secondImage!.size.width )
let newImageHeight = max(firstImage!.size.height, secondImage!.size.height)
let newImageSize = CGSize(width : newImageWidth, height: newImageHeight)
UIGraphicsBeginImageContextWithOptions(newImageSize, false, UIScreen.main.scale)
let firstImageDrawX = round((newImageSize.width - firstImage!.size.width ) / 2)
let firstImageDrawY = round((newImageSize.height - firstImage!.size.height ) / 2)
let secondImageDrawX = round((newImageSize.width - secondImage!.size.width ) / 2)
let secondImageDrawY = round((newImageSize.height - secondImage!.size.height) / 2)
firstImage!.draw(at: CGPoint(x: firstImageDrawX, y: firstImageDrawY))
secondImage!.draw(at: CGPoint(x: secondImageDrawX, y: secondImageDrawY), blendMode: .sourceAtop, alpha: 1.0)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
convenience init?(withContentsOfUrl url: URL) throws {
let imageData = try Data(contentsOf: url)
self.init(data: imageData)
}
}
其中 'firstImage' 是图片,'secondImage' 是框架,它会将顶部的框架裁剪到图像的边框,这样 none 框架就会流血从图像尺寸。这是我希望实现的将图像边缘裁剪到框架轮廓的行为;但是,如果我翻转顺序以使图像被裁剪到框架的边缘,整个图像就会消失,在框架图像的顶部留下一个正方形 'cutout'。
好的,我明白了。我正在使用以下功能:
public func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0) -> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * Double.pi) / CGFloat(sides)
let width = min(rect.size.width, rect.size.height)
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
var angle = CGFloat(rotationOffset)
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)))
for _ in 0..<sides {
angle += theta
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
path.close()
let bounds = path.bounds
let transform = CGAffineTransform(translationX: -bounds.origin.x + rect.origin.x + lineWidth / 2.0, y: -bounds.origin.y + rect.origin.y + lineWidth / 2.0)
path.apply(transform)
return path
}
public func createImage(layer: CALayer) -> UIImage! {
let size = CGSize(width: layer.frame.maxX, height: layer.frame.maxY)
UIGraphicsBeginImageContextWithOptions(size, layer.isOpaque, 0.0)
let ctx = UIGraphicsGetCurrentContext()!
layer.render(in: ctx)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
结合我的 UIImage 扩展:
extension UIImage {
class func imageByCombiningImage(firstImage: UIImage, secondImage: UIImage) -> UIImage {
let newImageWidth = max(firstImage.size.width, secondImage.size.width )
let newImageHeight = max(firstImage.size.height, secondImage.size.height)
let newImageSize = CGSize(width : newImageWidth, height: newImageHeight)
UIGraphicsBeginImageContextWithOptions(newImageSize, false, UIScreen.main.scale)
let firstImageDrawX = round((newImageSize.width - firstImage.size.width ) / 2)
let firstImageDrawY = round((newImageSize.height - firstImage.size.height ) / 2)
let secondImageDrawX = round((newImageSize.width - secondImage.size.width ) / 2)
let secondImageDrawY = round((newImageSize.height - secondImage.size.height) / 2)
firstImage.draw(at: CGPoint(x: firstImageDrawX, y: firstImageDrawY))
secondImage.draw(at: CGPoint(x: secondImageDrawX, y: secondImageDrawY))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
convenience init?(withContentsOfUrl url: URL) throws {
let imageData = try Data(contentsOf: url)
self.init(data: imageData)
}
}
制作我的装裱图片如下:
func getProfileIcon() -> UIImage {
let frameSrc = playerProfileObject.levelFrame ?? ""
let portraitSrc = playerProfileObject.portrait ?? ""
let frameImage = try? UIImage(withContentsOfUrl: URL(string: frameSrc)!)
let portraitImage = try? UIImage(withContentsOfUrl: URL(string: portraitSrc)!)
let path = roundedPolygonPath(rect: CGRect(x: 0.0, y: 0.0, width: 150.0, height: 150.0), lineWidth: CGFloat(2.0), sides: 6, cornerRadius: 15.0, rotationOffset: CGFloat(Double.pi / 2.0))
let imageLayer = CAShapeLayer()
imageLayer.frame = CGRect(x: 0.0, y: 0.0, width: path.bounds.width, height: path.bounds.height)
imageLayer.path = path.cgPath
imageLayer.fillColor = UIColor(patternImage: portraitImage!).cgColor
return UIImage.imageByCombiningImage(firstImage: createImage(layer: imageLayer), secondImage: frameImage!)
}
现在唯一的问题是,图像仍然从框架下方稍微突出,并且还有一点偏移。但这些问题并不像将图像剪裁成合适的形状那么难弄清楚。最后,我确实为图层蒙版绘制了一个六边形路径,但随着 swiftUI 中大量的混合模式和蒙版功能,我确信存在或将会有一种涉及更少处理步骤的更好方法。如果目前有人知道更好的方法,我仍然会感兴趣并非常感谢您与我分享!
抱歉缩进,即使他们希望您在这个小框中添加代码块,SO 也不允许您使用制表符缩进。
我有一个ZStack
这样的
ZStack {
Image("source")
Image("source")
}
其中一层是实心正方形图像,另一层是六边形图像,比它后面的图像略小;里面有一个六边形的透明区域,就像它后面的图像的框架。我正在尝试剪裁框架后面的图像,以便它仅显示在框架图像中心的透明区域中。 我尝试将帧图像声明为我在视图中使用的 var 并作为其背后图像的掩码,但这显然会将图像裁剪为帧的确切形状。我的想法是我可以反转我的框架图像的 alpha 通道以显示它后面的图片,它是透明的。但是我没有运气反转图像的 alpha 通道。 看起来是这样的:
ZStack {
let mImg = Image("source")
// img
Image("source")
.mask(mImg) //attempting to invert alpha channel of frame to be used here
// frame
mImg
}
在无法反转 alpha 通道之后,我还认为 blendMode
可能会起作用,将框架放在 img 后面,并将 blendMode 应用于 img,以便框架绘制在img 仅在框架透明的区域绘制,如下所示:
ZStack {
// frame
Image("source")
// img
Image("source")
.blenMode(.destinationOver)
}
这导致框架被绘制在顶部,但图像完全消失了。所以后来我读到 .destinationOut
它仅在顶部透明的地方显示底层。我认为:
ZStack {
// img
Image("source")
// frame
Image("source")
.blenMode(.destinationOver)
}
意味着只绘制底层 (img),但仅在顶层 (frame) 的区域是透明的。然后我可以在上面添加一个额外的框架层,因为它只会用于混合这些线条。然而,这种混合模式的行为与文档中所说的完全不同,实际上仍然绘制了两个图层..但是 img 图层顶部的框架是纯黑色..
不用说,我在这件事上浪费的时间比我愿意承认的还要多,我觉得我把一些简单的事情变得非常复杂,因为我遗漏了修改器顺序或混合或蒙版选项的一个小变化.
一如既往地非常感谢任何人可以就该主题提供的任何建议、信息或帮助。如果有人想尝试使用代码来解决问题,我还可以模拟一个简短的工作示例结构。感谢您随时为我提供先进的帮助。
示例图片:
更新:
所以我使用了这个 UIImage 扩展:
extension UIImage {
class func imageByCombiningImage(firstImageUrl: String, withImageUrl: String) -> UIImage {
let firstImage = try? UIImage(withContentsOfUrl: URL(string: firstImageUrl)!)
let secondImage = try? UIImage(withContentsOfUrl: URL(string: withImageUrl)!)
let newImageWidth = max(firstImage!.size.width, secondImage!.size.width )
let newImageHeight = max(firstImage!.size.height, secondImage!.size.height)
let newImageSize = CGSize(width : newImageWidth, height: newImageHeight)
UIGraphicsBeginImageContextWithOptions(newImageSize, false, UIScreen.main.scale)
let firstImageDrawX = round((newImageSize.width - firstImage!.size.width ) / 2)
let firstImageDrawY = round((newImageSize.height - firstImage!.size.height ) / 2)
let secondImageDrawX = round((newImageSize.width - secondImage!.size.width ) / 2)
let secondImageDrawY = round((newImageSize.height - secondImage!.size.height) / 2)
firstImage!.draw(at: CGPoint(x: firstImageDrawX, y: firstImageDrawY))
secondImage!.draw(at: CGPoint(x: secondImageDrawX, y: secondImageDrawY), blendMode: .sourceAtop, alpha: 1.0)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
convenience init?(withContentsOfUrl url: URL) throws {
let imageData = try Data(contentsOf: url)
self.init(data: imageData)
}
}
其中 'firstImage' 是图片,'secondImage' 是框架,它会将顶部的框架裁剪到图像的边框,这样 none 框架就会流血从图像尺寸。这是我希望实现的将图像边缘裁剪到框架轮廓的行为;但是,如果我翻转顺序以使图像被裁剪到框架的边缘,整个图像就会消失,在框架图像的顶部留下一个正方形 'cutout'。
好的,我明白了。我正在使用以下功能:
public func roundedPolygonPath(rect: CGRect, lineWidth: CGFloat, sides: NSInteger, cornerRadius: CGFloat, rotationOffset: CGFloat = 0) -> UIBezierPath {
let path = UIBezierPath()
let theta: CGFloat = CGFloat(2.0 * Double.pi) / CGFloat(sides)
let width = min(rect.size.width, rect.size.height)
let center = CGPoint(x: rect.origin.x + width / 2.0, y: rect.origin.y + width / 2.0)
let radius = (width - lineWidth + cornerRadius - (cos(theta) * cornerRadius)) / 2.0
var angle = CGFloat(rotationOffset)
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
path.move(to: CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta)))
for _ in 0..<sides {
angle += theta
let corner = CGPoint(x: center.x + (radius - cornerRadius) * cos(angle), y: center.y + (radius - cornerRadius) * sin(angle))
let tip = CGPoint(x: center.x + radius * cos(angle), y: center.y + radius * sin(angle))
let start = CGPoint(x: corner.x + cornerRadius * cos(angle - theta), y: corner.y + cornerRadius * sin(angle - theta))
let end = CGPoint(x: corner.x + cornerRadius * cos(angle + theta), y: corner.y + cornerRadius * sin(angle + theta))
path.addLine(to: start)
path.addQuadCurve(to: end, controlPoint: tip)
}
path.close()
let bounds = path.bounds
let transform = CGAffineTransform(translationX: -bounds.origin.x + rect.origin.x + lineWidth / 2.0, y: -bounds.origin.y + rect.origin.y + lineWidth / 2.0)
path.apply(transform)
return path
}
public func createImage(layer: CALayer) -> UIImage! {
let size = CGSize(width: layer.frame.maxX, height: layer.frame.maxY)
UIGraphicsBeginImageContextWithOptions(size, layer.isOpaque, 0.0)
let ctx = UIGraphicsGetCurrentContext()!
layer.render(in: ctx)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
结合我的 UIImage 扩展:
extension UIImage {
class func imageByCombiningImage(firstImage: UIImage, secondImage: UIImage) -> UIImage {
let newImageWidth = max(firstImage.size.width, secondImage.size.width )
let newImageHeight = max(firstImage.size.height, secondImage.size.height)
let newImageSize = CGSize(width : newImageWidth, height: newImageHeight)
UIGraphicsBeginImageContextWithOptions(newImageSize, false, UIScreen.main.scale)
let firstImageDrawX = round((newImageSize.width - firstImage.size.width ) / 2)
let firstImageDrawY = round((newImageSize.height - firstImage.size.height ) / 2)
let secondImageDrawX = round((newImageSize.width - secondImage.size.width ) / 2)
let secondImageDrawY = round((newImageSize.height - secondImage.size.height) / 2)
firstImage.draw(at: CGPoint(x: firstImageDrawX, y: firstImageDrawY))
secondImage.draw(at: CGPoint(x: secondImageDrawX, y: secondImageDrawY))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
convenience init?(withContentsOfUrl url: URL) throws {
let imageData = try Data(contentsOf: url)
self.init(data: imageData)
}
}
制作我的装裱图片如下:
func getProfileIcon() -> UIImage {
let frameSrc = playerProfileObject.levelFrame ?? ""
let portraitSrc = playerProfileObject.portrait ?? ""
let frameImage = try? UIImage(withContentsOfUrl: URL(string: frameSrc)!)
let portraitImage = try? UIImage(withContentsOfUrl: URL(string: portraitSrc)!)
let path = roundedPolygonPath(rect: CGRect(x: 0.0, y: 0.0, width: 150.0, height: 150.0), lineWidth: CGFloat(2.0), sides: 6, cornerRadius: 15.0, rotationOffset: CGFloat(Double.pi / 2.0))
let imageLayer = CAShapeLayer()
imageLayer.frame = CGRect(x: 0.0, y: 0.0, width: path.bounds.width, height: path.bounds.height)
imageLayer.path = path.cgPath
imageLayer.fillColor = UIColor(patternImage: portraitImage!).cgColor
return UIImage.imageByCombiningImage(firstImage: createImage(layer: imageLayer), secondImage: frameImage!)
}
现在唯一的问题是,图像仍然从框架下方稍微突出,并且还有一点偏移。但这些问题并不像将图像剪裁成合适的形状那么难弄清楚。最后,我确实为图层蒙版绘制了一个六边形路径,但随着 swiftUI 中大量的混合模式和蒙版功能,我确信存在或将会有一种涉及更少处理步骤的更好方法。如果目前有人知道更好的方法,我仍然会感兴趣并非常感谢您与我分享!
抱歉缩进,即使他们希望您在这个小框中添加代码块,SO 也不允许您使用制表符缩进。