【问题标题】:Why do PDFs resized in SwiftUI getting sharp edges?为什么在 SwiftUI 中调整大小的 PDF 会变得锐利?
【发布时间】:2022-03-09 12:45:52
【问题描述】:

我尝试在使用 Xcode 11.4 和 iOS 13.4 的启用 SwiftUI 的应用程序中包含一个 pdf。但是,当我调整 pdf 的大小时,它会变得边缘。我包含了两个版本的 pdf:一个大 pdf (icon.pdf) 和一个小 pdf (icon_small.pdf)。当我调整icon.pdf 的大小时,它会得到起始边缘,而icon_small.pdf 会得到平滑的边缘。这个问题也适用于我尝试过的所有其他 pdf。

这是我的代码:

struct ContentView: View {
    var body: some View {
        VStack {
            Spacer()
            Text("icon.pdf:")
            Image("icon")
                .resizable()
                .renderingMode(.template)
                .aspectRatio(contentMode: .fit)
                .frame(width: 27.0, height: 27.0)
            Spacer()
            Text("icon_small.pdf:")
            Image("icon_small")
            Spacer()
        }
    }
}

icon.pdficon_small.pdf 都具有以下资产设置:

  • 渲染为:模板图像
  • 调整大小:保留矢量数据
  • 设备:通用
  • 音阶:单音阶

pdf 可在此处获得:

【问题讨论】:

  • 既然是矢量图,为什么还要icon.pdficon_small.pdf呢?我的第一个猜测是icon.pdf 搞砸了。只需将icon_small.pdf 用于所有尺寸。这是一个矢量图像,它会根据需要进行缩放。
  • 两者都是矢量图形 (pdf)。我将两者都包括在内是为了说明由 SwiftUI 调整大小的 pdf 具有锐利的边缘。因此,在将它们包含在我的项目中之前,我必须手动调整它们的大小。但是,据我了解,SwiftUI 应该能够调整图像大小而不会使边缘变得锐利。
  • hm...我尝试了一个大型矢量图像并调整了它的大小,它确实像素化了。一定是SwiftUI 中的矢量图像支持存在问题。让我们看看其他人有什么要说的。
  • 在 macOS 上也是如此
  • 它似乎仍然无法按预期工作。你是在什么地方报告的吗?

标签: pdf swiftui image-resizing vector-graphics


【解决方案1】:

我使用您提供的矢量图像对两个矢量图像进行了并排比较:

起初,我使用了SwiftUI 的内置Image,如前所述,两者的极端表现都很糟糕:

  • 大图缩小时边缘锐利
  • 小图放大后变得模糊

起初我认为这可能是您的 pdf 矢量,所以我使用了我知道在我以前的项目中运行良好的矢量,但我遇到了同样的问题。
认为这是一个UIImage 问题,我使用了SwiftUIs Image(uiImage:) 但同样的问题。

最后的猜测是图像容器,并且知道UIImageView 已经很好地处理了矢量图像,让UIViewRepresentable 包装UIImageView 似乎可以解决这个问题。目前看来,这似乎是一种可能的解决方法。

变通解决方案:

struct MyImageView: UIViewRepresentable {
  var name: String
  var contentMode: UIView.ContentMode = .scaleAspectFit
  var tintColor: UIColor = .black

  func makeUIView(context: Context) -> UIImageView {
    let imageView = UIImageView()
    imageView.setContentCompressionResistancePriority(.fittingSizeLevel, 
                                                      for: .vertical)
    return imageView
  }

  func updateUIView(_ uiView: UIImageView, context: Context) {
    uiView.contentMode = contentMode
    uiView.tintColor = tintColor
    if let image = UIImage(named: name) {
      uiView.image = image
    }
  }
}

这会丢失一些SwiftUI Image 修饰符(您仍然有正常的View 修饰符),但您始终可以传入一些参数,例如contentModetintColor,如上所示。如果需要,添加更多并进行相应处理。


使用示例:

struct ContentView: View {
  var body: some View {
    VStack {
      MyImageView(name: "icon", //REQUIRED
                  contentMode: .scaleAspectFit, //OPTIONAL
                  tintColor: .black /*OPTIONAL*/)
        .frame(width: 27, height: 27)
      MyImageView(name: "icon_small", //REQUIRED
                  contentMode: .scaleAspectFit, //OPTIONAL
                  tintColor: .black /*OPTIONAL*/)
        .frame(width: 27, height: 27)
    }
  }
}

现在这都是猜测,但看起来SwiftUI 将矢量图像视为PNG

以下示例是在UIKitUIImageViewSwiftUIImage 中渲染的小型和大型矢量图像的简单并排比较。

比较:

struct ContentView: View {
  let (largeImage, smallImage) = ("icon", "icon_small")
  let range = stride(from: 20, to: 320, by: 40).map { CGFloat($0) }

  var body: some View {
    List(range, id: \.self) { (side) in
      ScrollView(.horizontal) {
        VStack(alignment: .leading) {
          Text(String(format: "%gx%g", side, side))
          HStack {
            VStack {
              Text("UIKit")
              MyImageView(name: self.smallImage)
                .frame(width: side, height: side)
              MyImageView(name: self.largeImage)
                .frame(width: side, height: side)
            }
            VStack {
              Text("SwiftUI")
              Image(self.smallImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: side)
              Image(self.largeImage)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: side)
            }
          }
        }
      }
    }
  }
}

结果:

  1. 顶行;左:UIImageView 中的小图片
  2. 顶行;右:SwiftUI Image 中的小图片
  3. 底行;左:UIImageView 中的大图
  4. 底行;右:SwiftUI Image 中的大图

UIKitUIImageView 具有一致的性能,而 SwiftUIImage 有问题。




【讨论】:

    【解决方案2】:

    PDF 矢量需要通过UIGraphicsBeginImageContextWithOptions 以编程方式调整大小,以便在放大(或缩小)它们时不会显示模糊。无需拥有多个具有不同分辨率的 PDF 即可完成此操作。

    不幸的是,这不是由 UIKit 或 SwiftUI 自动完成的。这是一个示例,其中 24x24 PDF 矢量被着色并调整为 200x200。

    Image(uiImage: UIImage(named: "heart")!.tinted(withColor: .blue,
                                                   biggerSize: CGSize(width: 200, height: 200)))
          .resizable()
          .frame(width: 200, height: 200,
                 alignment: .center)
    
    extension UIImage {
    
        /// Uses compositor blending to apply color to an image. When an image is too small it will be shown
        /// blurred. So you have to provide a size property to get a good resolution image
        public func tinted(withColor: UIColor?, biggerSize: CGSize = .zero) -> UIImage {
            guard let withColor = withColor else { return self }
            
            let size = biggerSize == .zero ? self.size : biggerSize
            let img2 = UIImage.createWithColor(size, color: withColor)
            let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
            let renderer = UIGraphicsImageRenderer(size: size)
            let result = renderer.image { _ in
                img2.draw(in: rect, blendMode: .normal, alpha: 1)
                draw(in: rect, blendMode: .destinationIn, alpha: 1)
            }
            return result
        }
    
        public static func createWithColor(_ size: CGSize, color: UIColor) -> UIImage {
            UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
            let context = UIGraphicsGetCurrentContext()
            let rect = CGRect(size: size)
            color.setFill()
            context!.fill(rect)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return image!
        }
    }
    

    【讨论】:

      【解决方案3】:

      这两个 PDF 文件之间基本上没有区别,只是在一种情况下内容的坐标被缩放了 ~14.2 倍。

      我猜想区别不在于 PDF 文件,而在于您用来绘制内容的渲染引擎。请注意,PDF 文件使用透明度(它具有 0.4 的恒定 alpha),因此混合计算可能会导致边缘处的结果略有不同。

      在 Adob​​e Acrobat 中查看这两个文件,在屏幕上缩放为相同大小,它们之间没有明显的区别。

      放大到您的 PNG 文件,我看到 icon_small.pdf 具有抗锯齿边缘,而 icon.pdf 没有。您没有说您使用什么将 PDF 文件呈现为 PNG,但我认为您将不得不与任何工具的作者讨论它。

      【讨论】:

      • 两张图片都是 pdf 格式——这里没有 png。 pdf 最初是 svg,使用 CairoSVG:cairosvg icon.svg -o icon.pdf 转换为 pdf。 icon_small.pdf 是使用 cairosvg icon.svg -o icon_small.pdf --output-width 36 --output-height 36 创建的。
      • 我所指的PNG是您问题中的那个,它展示了(我假设)“锐边”。当使用 Acrobat 以相同大小呈现时,PDF 文件是相同的,并且内容是相同的(除了缩放)。 PNG还有其他细节(电话的东西?)我假设它是一个屏幕截图。无论将两个 PDF 文件(不是位图意义上的图像)渲染到屏幕上,它们的渲染方式都不同,大概是因为一个是另一个的 14 倍,但它们显示在同一个屏幕区域。 “问题”是渲染而不是 PDF 文件。
      • 啊,我明白了。 PNG 只是 iPhone 8 模拟器的屏幕截图。但同样的情况发生在我的 iPhone 8 上。我同意 pdf 文件没有任何问题。所以问题(和解决方案)可能在 SwiftUI 中。
      【解决方案4】:

      同样的问题出现在这篇文章中: Xcode 11 PDF image assets "Preserve Vector Data" not working in SwiftUI?

      来自链接的 UIKit 解决方案:

      let uiImage = UIImage(named: "Logo-vector")!
      var image: Image {
              Image(uiImage: uiImage.resized(to: CGSize(width: 500, height: 500)))
                  .resizable()
      }
      
      var body: some View {
              NavigationView {
                  VStack(alignment: .center, spacing: 8) {
                      Spacer()
                      Text("Logo vector SwiftUI")
                      image
                          .frame(width: 240, height: 216)
                      ...
                      }
                      ...
                  }
              }
      }
      
      extension UIImage {
          func resized(to size: CGSize) -> UIImage {
              return UIGraphicsImageRenderer(size: size).image { _ in
                  draw(in: CGRect(origin: .zero, size: size))
              }
          }
      }
      

      我整理了一个名为 ResizableVector 的跨平台框架,它执行相同的大小调整方法:

      extension PlatformImage {
          func resized(to size: CGSize) -> PlatformImage {
              return GraphicsImageRenderer(size: size).image { _ in
                  self.draw(in: CGRect(origin: .zero, size: size))
              }
          }
      }
      

      其中 PlatformImage 只是 UIImage 或 NSImage 的类型别名,GraphicsImageRenderer 是 UIGraphicsImageRenderer 或 MacGraphicsImageRenderer 的类型别名。

      框架提供了一个名为 ResizableVector 的 SwiftUI 视图来代替 Image。如果需要,它还可以尊重原始纵横比。

      您可以使用 SPM 添加它 - 在 GitHub 上查看: https://github.com/Matt54/ResizableVector

      【讨论】:

        猜你喜欢
        • 2021-10-07
        • 2018-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-23
        • 1970-01-01
        • 2021-04-20
        相关资源
        最近更新 更多