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 年,但它是我第一个使用 ios 和UIKit
的应用程序,所以起初我使用了滚动视图和页面控件,然后才意识到分段控件会有多好。我的最新应用程序几乎需要类似的东西 - 我正在考虑使用缩略图,但到目前为止认为这对于以后的版本更好。我可以添加两个性能批评吗?根据您发布的代码....
(1) 想办法只使用一个CIContext
。我看到您正在循环创建一个。它可以轻松共享,而且创建成本非常。 (2) 考虑考虑让您的CIImages
在与主线程不同的线程中。老实说,我不知道这是否可能,但是如果/当我希望为表格视图创建 12-57 个缩略图时,我会发现。奖励:(3) 如果您还没有发现,在 iOS 模拟器上使用 CoreImage 时,请了解性能有多非常差。如果您希望看到真实的性能,请使用实际设备。总而言之,您的代码看起来不错。以上是关于iOS - 创建核心图像过滤器循环时保留原始图像的主要内容,如果未能解决你的问题,请参考以下文章