在 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 也不允许您使用制表符缩进。