【问题标题】:Masking an image to the transparent section of another Image in SwiftUI在 SwiftUI 中将图像遮盖到另一个图像的透明部分
【发布时间】:2021-08-29 12:05:19
【问题描述】:

我有一个像这样的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)会被绘制,但只有在顶层(框架)是透明的区域。然后我可以在其上添加一个额外的框架层,因为它只会用于混合这些线条。但是,这种混合模式与文档中所说的完全不同,实际上仍然会绘制两个图层.. 但是 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' 是框架,它将框架顶部裁剪到图像的边界,这样为什么没有框架从图像尺寸中溢出。这是我希望实现的将图像边缘裁剪为框架轮廓的行为;但是,如果我翻转顺序以使图像被裁剪到框架的边缘,整个图像就会消失,在框架图像的顶部留下一个方形的“切口”。

【问题讨论】:

  • 你能上传一些样本图片进行测试吗?还有为什么两张图片都命名为"source"
  • source 不是名称,只是图像来源的通用占位符,但我可以稍后上传一些示例。
  • 所以你想将宇航员图像剪辑到六边形的内部?
  • 我添加了示例图片。这个框架不是我使用的确切框架图像,因为我是从互联网上提取的,只需要一个简单的例子,但它是精确的形状,并且在形状的中心和外部具有相同的透明度。唯一的区别是我的框架在形状边缘周围有一个设计,使它更厚一点。
  • @aheze 准确地说.. 我最初将图像作为框架图像的背景,但这会导致它从下面的边缘流出.. 我将它剪裁成一个适用于某些图像的圆圈,但是不是全部。我想我可以制作一个自定义路径,绘制一个与我的框架和剪辑形状相同方向的实心六边形,但我不知道如何确保它与我的框架图像内部的尺寸完全相同,并且似乎应该有一种更简单的方法来减少应用程序的工作量

标签: swift image swiftui masking


【解决方案1】:

好的,我想通了。我正在使用以下功能:

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 中的 blendModes 和蒙版功能的数量,我确信存在或将会有更好的方法来减少处理步骤。如果目前有人知道更好的方法,我仍然会很感兴趣并非常感谢您与我分享!

抱歉缩进,即使他们希望您在这个小框中添加代码块,SO 也不允许您使用缩进标签。

【讨论】:

  • 我现在已经简化了解决方案,删除了不必要的转换和组合功能,因为我目前对整个过程只有一个用途。仍然需要修复填充和偏移
  • 我正在尝试这样做,但是对于我在情节提要中的 UIImage,您是否找到了一种方法使其成为 UI Image 扩展的可调用属性?
  • 我对图像所做的一切修改基本上都是使用 UIKit,它甚至返回了一个 UIImage,我只是在 SwiftUI 的“图像”中显示。该扩展实际上是一个 UIImage 扩展,应该在您的情况,因为它返回您实际使用的内容。
猜你喜欢
  • 1970-01-01
  • 2011-07-20
  • 2016-03-12
  • 2020-05-31
  • 1970-01-01
  • 2016-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多