iOS - 创建核心图像过滤器循环时保留原始图像

Posted

技术标签:

【中文标题】iOS - 创建核心图像过滤器循环时保留原始图像【英文标题】:iOS -Keep original image when creating a loop of Core Image Filters 【发布时间】:2018-09-10 20:33:31 【问题描述】:

我遵循了这两个教程tutsplus 和appcoda

我在 vc1 中有一个图像,我将其传递给 vc2。 vc2 有两个 imageViews:originalImageView 显示来自 vc1 的原始图像,filteredImageView 显示应用了核心图像过滤器的新图像。 filteredImageView 锚定在 originalImageView 之上

我还有一个滚动视图,我使用 as 按钮创建 coreImage 过滤器,一旦按下按钮,就会应用新过滤器,过滤后的图像现在显示在 filteredImageView 内,它隐藏了 originalImageView,因为它位于它。

一旦用户按下任何包含过滤器的按钮,他们就无法返回到原始图像。

如何创建一个按钮来显示滚动视图中的原始图像以及所有过滤后的图像?理想情况下,它将是第一张图片,这样一旦用户按下它,就会显示原始图片

let originalImageView: UIImageView = 
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFill
    return imageView
()

let filteredImageView: UIImageView = 
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFill
    return imageView
()

let scrollView: UIScrollView = 
    let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.backgroundColor = .black
    return scrollView
()

var originalImage: UIImage? // set from vc1

let ciFilterNames = [
    "CISharpenLuminance",
    "CIPhotoEffectChrome",
    "CIPhotoEffectFade",
    "CIPhotoEffectInstant",
    "CIPhotoEffectNoir",
    "CIPhotoEffectProcess",
    "CIPhotoEffectTonal",
    "CIPhotoEffectTransfer",
    "CISepiaTone"
]

override func viewDidLoad() 
    super.viewDidLoad()

    createAnchors()

    guard let originalImage = originalImage else  return 

    originaImage.image = originalImage

    var xCoord: CGFloat = 5
    let yCoord: CGFloat = 5
    let buttonWidth:CGFloat = 70
    let buttonHeight: CGFloat = 70
    let gapBetweenButtons: CGFloat = 5

    var itemCount = 0

    for i in 0..<ciFilterNames.count 

        itemCount = i

        let filterButton = UIButton(type: .custom)
        button.frame = CGRect(x: xCoord, y: yCoord, width: buttonWidth, height: buttonHeight)
        filterButton.tag = itemCount
        filterButton.addTarget(self, action: #selector(filterButtonPressed(_:)), for: .touchUpInside)
        filterButton.layer.cornerRadius = 7
        filterButton.clipsToBounds = true

        guard let openGLContext = EAGLContext(api: .openGLES3) else  return 
        let context = CIContext(eaglContext: openGLContext)

        // Create filters for each button
        let coreImage = CIImage(image: originalImage)
        let filter = CIFilter(name: "\(ciFilterNames[i])" )
        filter?.setDefaults()
        filter?.setValue(coreImage, forKey: kCIInputImageKey)

        if ciFilterNames[i] == "CISharpenLuminance" 
            filter?.setValue(0.8, forKey: kCIInputSharpnessKey)
         else 
            filter?.setValue(1, forKey: kCIInputIntensityKey)
        

        guard let output = filter?.value(forKey: kCIOutputImageKey) as? CIImage else  return 
        guard let cgimgresult = context.createCGImage(output, from: output.extent) else  return 
        let imageForButton = UIImage(cgImage: cgimgresult)
        filterButton.setBackgroundImage(imageForButton, for: .normal)

        // Add Buttons in the Scroll View
        xCoord += buttonWidth + gapBetweenButtons
        scrollView.addSubview(filterButton)
    


@objc fileprivate func filterButtonPressed(_ sender: UIButton) 

    let button = sender as UIButton

    filteredImageView.image = button.backgroundImage( for: .normal)


func createAnchors() 

    view.addSubview(originalImageView)
    view.addSubview(filteredImageView)
    view.addSubview(scrollView)

    originalImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    originalImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    originalImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    originalImageView.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
    originalImageView.heightAnchor.constraint(equalToConstant: view.frame.width).isActive = true

    filteredImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    filteredImageView.leadingAnchor.constraint(equalTo: originalImageView.leadingAnchor).isActive = true
    filteredImageView.widthAnchor.constraint(equalTo: originalImageView.widthAnchor).isActive = true
    filteredImageView.heightAnchor.constraint(equalTo: originalImageView.heightAnchor).isActive = true

    scrollView.topAnchor.constraint(equalTo: originalImageView.bottomAnchor, constant: 8).isActive = true
    scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    scrollView.heightAnchor.constraint(equalToConstant: 80).isActive = true

【问题讨论】:

您不需要有两个重叠的图像视图。相反,您可能只有一个图像视图并保留两个图像。要更改当前图像,请使用 UIImageView 的“图像”属性 是有道理的,但是一旦应用了过滤器并且用户想要回到原来的图像,我该如何回到原始图像? 将原始图像存储在 var 中,并在您想要重置时将其设置回来 @Sh_Khan 问题是原始图像未与过滤器一起显示。我可以添加一个 barButtonItem,它会在按下时将其还原,但它看起来很奇怪,而不是显示原始图像和过滤后的图像。顺便说一句,将原始图像保存在一个完全独立的属性中是个好主意 关于滚动。尝试 UITableView,其中每个图像都是一个单元格。 【参考方案1】:

我使用水平collectionView和viewDidLoad而不是scrollView,第一次我将来自vc1的图像添加到[UIImages]的数组中,然后第二次我循环遍历所有ciFilterNames并将它们添加到数组中,就像我添加它们一样到滚动视图。这样做可以让我将原始图像作为第一张图像以及所有过滤后的图像一起放在 collectionVIew 中。我也只需要一个 imageView。

我在 viewDidLoad 里面的 cmets 中添加了这两个步骤

var collectionView: UICollectionView!

let originalImageView: UIImageView = 
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.contentMode = .scaleAspectFill
    return imageView
()

var originalImage: UIImage? // set from vc1 or just use your own UIImage(named: "whatever") for this example
var tableData = [UIImage]()
let filterCell = "filterCell"

let ciFilterNames = [
    "CISharpenLuminance",
    "CIPhotoEffectChrome",
    "CIPhotoEffectFade",
    "CIPhotoEffectInstant",
    "CIPhotoEffectNoir",
    "CIPhotoEffectProcess",
    "CIPhotoEffectTonal",
    "CIPhotoEffectTransfer",
    "CISepiaTone"
]

override func viewDidLoad() 
    super.viewDidLoad()

    configureCollectionView()
    createAnchors()

    guard let originalImage = originalImage else  return 

    originaImage.image = originalImage

    // 1. this appends the originalImage as the very first cell
    tableData.append(originaImage)

    guard let openGLContext = EAGLContext(api: .openGLES3) else  return 
    let context = CIContext(eaglContext: openGLContext)

    for i in 0..<ciFilterNames.count 

        let coreImage = CIImage(image: tableData[0])
        let filter = CIFilter(name: "\(ciFilterNames[i])" )
        filter?.setDefaults()
        filter?.setValue(coreImage, forKey: kCIInputImageKey)

        if ciFilterNames[i] == "CISharpenLuminance" 
            filter?.setValue(0.8, forKey: kCIInputSharpnessKey)
         else if ciFilterNames[i] == "CISepiaTone" 
            filter?.setValue(1, forKey: kCIInputIntensityKey)
         else 
            filter?.value(forKey: kCIOutputImageKey) as! CIImage
        

        guard let output = filter?.value(forKey: kCIOutputImageKey) as? CIImage else  return 
        guard let cgimgresult = context.createCGImage(output, from: output.extent) else  return 
        let newImageWithFilterApplied = UIImage(cgImage: cgimgresult)

        // 2. after each loop append the newImageWithFilterApplied to the rest of the cells
        tableData.append(newImageWithFilterApplied)
        collectionView.reloadData()
    


func configureCollectionView() 
    let layout = UICollectionViewFlowLayout()
    layout.sectionInset = UIEdgeInsetsMake(0, 0, 0, 0)
    layout.scrollDirection = .horizontal

    collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
    collectionView.translatesAutoresizingMaskIntoConstraints = false
    collectionView.delegate = self
    collectionView.dataSource = self
    collectionView.alwaysBounceHorizontal = true
    collectionView.backgroundColor = .black
    collectionView.showsHorizontalScrollIndicator = false
    collectionView.register(FilterCell.self, forCellWithReuseIdentifier: filterCell)


func createAnchors() 

    view.addSubview(originalImageView)
    view.addSubview(collectionView)

    originalImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    originalImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    originalImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    originalImageView.heightAnchor.constraint(equalToConstant: view.frame.width).isActive = true

    collectionView.topAnchor.constraint(equalTo: originalImageView.bottomAnchor, constant: 10).isActive = true
    collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    collectionView.heightAnchor.constraint(equalToConstant: 80).isActive = true


func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
    return tableData.count


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
    return CGSize(width: 80, height: 80)


func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat 
    return 10


func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 

    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: filterCell, for: indexPath) as! FilterCell

    cell.imageView.image = tableData[indexPath.item]

    return cell


func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
    guard let cell = collectionView.cellForItem(at: indexPath) as? FilterCell else  return 
    guard let indexPath = collectionView.indexPath(for: cell) else  return 

    originalImageView.image = tableData[indexPath.item]

【讨论】:

感谢您回答您自己的问题(人们常常没有意识到您可以并且*应该)。如果您提供一些代码,我会很高兴地支持它。 (实际上我曾经确实有一个滚动视图,但由于我的需求是“原始/最终”,因此我使用了分段控件。) 您好,感谢您的评论。我是自学成才的,经过多年的学习,我意识到这个世界上有人会遇到这个问题。我在 SO 上得到了很多帮助,所以我想回馈一下。今晚晚些时候我会上传代码然后给你发消息。您的需求与我的需求相同吗?您是如何对所有这些过滤器使用分段控件的?似乎只能在 iPad 或横向模式下使用。 @dfd 我添加了代码,你可以 c+p 但添加一个 collectionViewCell,将其命名为 FilterCell 并添加一个 imageView。另外不要忘记为 originalImage 提供实际图像,否则保护语句将在 viewDidLoad 中返回。 我真的不应该在没有拼写检查的情况下发布 cmets! :-) 无论如何,我的需求更简单——在我创建的单个过滤器/内核之前/之后。虽然我从事编码工作 35 年,但它是我第一个使用 iosUIKit 的应用程序,所以起初我使用了滚动视图和页面控件,然后才意识到分段控件会有多好。我的最新应用程序几乎需要类似的东西 - 我正在考虑使用缩略图,但到目前为止认为这对于以后的版本更好。我可以添加两个性能批评吗?根据您发布的代码.... (1) 想办法使用一个CIContext。我看到您正在循环创建一个。它可以轻松共享,而且创建成本非常。 (2) 考虑考虑让您的CIImages 在与主线程不同的线程中。老实说,我不知道这是否可能,但是如果/当我希望为表格视图创建 12-57 个缩略图时,我会发现。奖励:(3) 如果您还没有发现,在 iOS 模拟器上使用 CoreImage 时,请了解性能有多非常差。如果您希望看到真实的性能,请使用实际设备。总而言之,您的代码看起来不错。

以上是关于iOS - 创建核心图像过滤器循环时保留原始图像的主要内容,如果未能解决你的问题,请参考以下文章

核心图像过滤器 iOS UISlider

核心图像过滤器的输出图像第二次在 UIImage 上运行时为零

iOS 标签栏项自定义图像

如何解码图像并将原始图像保留在 Go 中?

UITableview 单元格保留自定义图像视图过滤器

带有裁剪参数的拇指上传