iOS8 Core Image In Swift:自動改善圖像以及內置濾鏡的使用
上面那篇文章主要是Core Image的基礎,只是為了說明CIImage、CIFilter、CIContext,以及基礎濾鏡的簡單使用。在上一篇中幾乎沒有對濾鏡進行更復雜的操作,都是直接把inputImage扔給CIFilter而已,而Core Image實際上還能對濾鏡進行更加細粒度的控制,我們在新的工程中對其進行探索。為此,我重新建立了一個空的workspace,並把之前所使用的工程添加到這個workspace中,編譯、運行,沒問題的話我們就開始創建新的工程。
通過workspace左下角的Add Files to添加已有的工程:
當添加工程到workspace的時候,記得要把被添加的工程關掉,不然workspacce不能識別。
用Single View Application的工程模板建立一個新的工程,在View上放一個UIImageView,還是同樣的frame,同樣的ContentMode設置為Aspect Fit,同樣的關閉Auto Layout以及Size Classes,最後把上個工程中使用的圖片復制過來,在這個工程中同樣使用這張圖。
做完上面這些基礎工作後,我們回到VC中,把showFiltersInConsole方法從上個工程中復制過來,然後在viewDidLoad裡調用,在運行之前我們先看看Core Image有哪些類別,畢竟全部的濾鏡有127種,不可能一一用到的。
類別有很多,而且我們從上一篇中知道了濾鏡可以同時屬於不同的類別,除此之外,類別還分為兩大類:
kCICategoryDistortionEffect 扭曲效果,比如bump、旋轉、holekCICategoryGeometryAdjustment 幾何開著調整,比如仿射變換、平切、透視轉換kCICategoryCompositeOperation 合並,比如源覆蓋(source over)、最小化、源在頂(source atop)、色彩混合模式kCICategoryHalftoneEffect Halftone效果,比如screen、line screen、hatchedkCICategoryColorAdjustment 色彩調整,比如伽馬調整、白點調整、曝光kCICategoryColorEffect 色彩效果,比如色調調整、posterizekCICategoryTransition 圖像間轉換,比如dissolve、disintegrate with mask、swipekCICategoryTileEffect 瓦片效果,比如parallelogram、trianglekCICategoryGenerator 圖像生成器,比如stripes、constant color、checkerboardkCICategoryGradient 漸變,比如軸向漸變、仿射漸變、高斯漸變kCICategoryStylize 風格化,比如像素化、水晶化kCICategorySharpen 銳化、發光kCICategoryBlur 模糊,比如高斯模糊、焦點模糊、運動模糊
class ViewController: UIViewController {
@IBOutlet var imageView: UIImageView!
@IBOutlet var slider: UISlider!
lazy var originalImage: UIImage = {
return UIImage(named: Image)
}()
lazy var context: CIContext = {
return CIContext(options: nil)
}()
var filter: CIFilter!
......
與之前工程中不同的是,我多加了一個UISlider,Main.storyboard中VC的view像這樣:把UIImageView及UISlider的連線與VC中的連接起來,然後我們在viewDidLoad方法裡寫上:
override func viewDidLoad() {
super.viewDidLoad()
imageView.layer.shadowOpacity = 0.8
imageView.layer.shadowColor = UIColor.blackColor().CGColor
imageView.layer.shadowOffset = CGSize(width: 1, height: 1)
slider.maximumValue = Float(M_PI)
slider.minimumValue = Float(-M_PI)
slider.value = 0
slider.addTarget(self, action: valueChanged, forControlEvents: UIControlEvents.ValueChanged)
let inputImage = CIImage(image: originalImage)
filter = CIFilter(name: CIHueAdjust)
filter.setValue(inputImage, forKey: kCIInputImageKey)
slider.sendActionsForControlEvents(UIControlEvents.ValueChanged)
showFiltersInConsole()
}
imageView的設置同以前一樣,增加點陰影顯得好看多了。接著對slider初始化,在之前我們了解到CIHueAdjust濾鏡的inputAngle參數最大值是?,最小值是負?,默認值是0,就用這些值來初始化,然後添加一個當值發生改變時觸發的事件。
初始化filter,由於只有一個濾鏡,filter對象也可以重用,設置完inputImage後,觸發slider的事件就可以了。
valueChanged方法實現:
@IBAction func valueChanged() {
filter.setValue(slider.value, forKey: kCIInputAngleKey)
let outputImage = filter.outputImage
let cgImage = context.createCGImage(outputImage, fromRect: outputImage.extent())
imageView.image = UIImage(CGImage: cgImage)
}
filter會在每次觸發這個事件的時候更新inputAngle屬性,同時輸出到imageView上。
雖然我並不是在Storyboard裡把slider的valueChanged事件連接到VC的方法上,但是在這裡使用@IBAction也是適當的,這樣可以表明這個方法不是業務邏輯方法,而是一個UI控件觸發的方法。
編譯、運行,應該可以看到效果了。
這裡是oldFilmEffect方法實現:
@IBAction func oldFilmEffect() {
let inputImage = CIImage(image: originalImage)
// 1.創建CISepiaTone濾鏡
let sepiaToneFilter = CIFilter(name: CISepiaTone)
sepiaToneFilter.setValue(inputImage, forKey: kCIInputImageKey)
sepiaToneFilter.setValue(1, forKey: kCIInputIntensityKey)
// 2.創建白斑圖濾鏡
let whiteSpecksFilter = CIFilter(name: CIColorMatrix)
whiteSpecksFilter.setValue(CIFilter(name: CIRandomGenerator).outputImage.imageByCroppingToRect(inputImage.extent()), forKey: kCIInputImageKey)
whiteSpecksFilter.setValue(CIVector(x: 0, y: 1, z: 0, w: 0), forKey: inputRVector)
whiteSpecksFilter.setValue(CIVector(x: 0, y: 1, z: 0, w: 0), forKey: inputGVector)
whiteSpecksFilter.setValue(CIVector(x: 0, y: 1, z: 0, w: 0), forKey: inputBVector)
whiteSpecksFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 0), forKey: inputBiasVector)
// 3.把CISepiaTone濾鏡和白斑圖濾鏡以源覆蓋(source over)的方式先組合起來
let sourceOverCompositingFilter = CIFilter(name: CISourceOverCompositing)
sourceOverCompositingFilter.setValue(whiteSpecksFilter.outputImage, forKey: kCIInputBackgroundImageKey)
sourceOverCompositingFilter.setValue(sepiaToneFilter.outputImage, forKey: kCIInputImageKey)
// ---------上面算是完成了一半
// 4.用CIAffineTransform濾鏡先對隨機噪點圖進行處理
let affineTransformFilter = CIFilter(name: CIAffineTransform)
affineTransformFilter.setValue(CIFilter(name: CIRandomGenerator).outputImage.imageByCroppingToRect(inputImage.extent()), forKey: kCIInputImageKey
affineTransformFilter.setValue(NSValue(CGAffineTransform: CGAffineTransformMakeScale(1.5, 25)), forKey: kCIInputTransformKey)
// 5.創建藍綠色磨砂圖濾鏡
let darkScratchesFilter = CIFilter(name: CIColorMatrix)
darkScratchesFilter.setValue(affineTransformFilter.outputImage, forKey: kCIInputImageKey)
darkScratchesFilter.setValue(CIVector(x: 4, y: 0, z: 0, w: 0), forKey: inputRVector)
darkScratchesFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 0), forKey: inputGVector)
darkScratchesFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 0), forKey: inputBVector)
darkScratchesFilter.setValue(CIVector(x: 0, y: 0, z: 0, w: 0), forKey: inputAVector)
darkScratchesFilter.setValue(CIVector(x: 0, y: 1, z: 1, w: 1), forKey: inputBiasVector)
// 6.用CIMinimumComponent濾鏡把藍綠色磨砂圖濾鏡處理成黑色磨砂圖濾鏡
let minimumComponentFilter = CIFilter(name: CIMinimumComponent)
minimumComponentFilter.setValue(darkScratchesFilter.outputImage, forKey: kCIInputImageKey)
// ---------上面算是基本完成了
// 7.最終組合在一起
let multiplyCompositingFilter = CIFilter(name: CIMultiplyCompositing)
multiplyCompositingFilter.setValue(minimumComponentFilter.outputImage, forKey: kCIInputBackgroundImageKey)
multiplyCompositingFilter.setValue(sourceOverCompositingFilter.outputImage, forKey: kCIInputImageKey)
// 8.最後輸出
let outputImage = multiplyCompositingFilter.outputImage
let cgImage = context.createCGImage(outputImage, fromRect: outputImage.extent())
imageView.image = UIImage(CGImage: cgImage)
}
以上就是一個老電影濾鏡的“配方”了。編譯、運行,顯示效果如下:class CIColorInvert: CIFilter {
var inputImage: CIImage!
override var outputImage: CIImage! {
get {
return CIFilter(name: CIColorMatrix, withInputParameters: [
kCIInputImageKey : inputImage,
inputRVector : CIVector(x: -1, y: 0, z: 0),
inputGVector : CIVector(x: 0, y: -1, z: 0),
inputBVector : CIVector(x: 0, y: 0, z: -1),
inputBiasVector : CIVector(x: 1, y: 1, z: 1),
]).outputImage
}
}
}
然後在Storyboard的VC上增加一個按鈕“反色”,連接到VC的colorInvert方法上,colorInvert方法實現如下:@IBAction func colorInvert() {
let colorInvertFilter = CIColorInvert()
colorInvertFilter.inputImage = CIImage(image: imageView.image)
let outputImage = colorInvertFilter.outputImage
let cgImage = context.createCGImage(outputImage, fromRect: outputImage.extent())
imageView.image = UIImage(CGImage: cgImage)
}
這樣一下,一個對Core Image預置濾鏡的簡單封裝就完成了,每一個濾鏡的效果就像是一張配方,CIFilter就是裝有配方的瓶子,所以子類化CIFilter並不算自定義濾鏡,但是從iOS 8開始,Core Image是支持真正的自定義濾鏡的,自定義的濾鏡被稱之為內核(CIKernel),在WWDC視頻裡對其有50分鐘的介紹:https://developer.apple.com/videos/wwdc/2014/#515。運行後反色的效果,再次點擊反色按鈕後顯示原圖:......
lazy var originalImage: UIImage = {
return UIImage(named: Image2)
}()
......
然後在Storyboard的VC上增加兩個按鈕:一個用於顯示原圖:@IBAction func showOriginalImage() {
self.imageView.image = originalImage
}
另一個按鈕就叫“更換背景”,連接到VC的IBAction方法replaceBackground上。我們先看要做的事情:消除深綠色組合圖片struct CubeMap {
int length;
float dimension;
float *data;
};
struct CubeMap createCubeMap(float minHueAngle, float maxHueAngle) {
const unsigned int size = 64;
struct CubeMap map;
map.length = size * size * size * sizeof (float) * 4;
map.dimension = size;
float *cubeData = (float *)malloc (map.length);
float rgb[3], hsv[3], *c = cubeData;
for (int z = 0; z < size; z++){
rgb[2] = ((double)z)/(size-1); // Blue value
for (int y = 0; y < size; y++){
rgb[1] = ((double)y)/(size-1); // Green value
for (int x = 0; x < size; x ++){
rgb[0] = ((double)x)/(size-1); // Red value
rgbToHSV(rgb,hsv);
// Use the hue value to determine which to make transparent
// The minimum and maximum hue angle depends on
// the color you want to remove
float alpha = (hsv[0] > minHueAngle && hsv[0] < maxHueAngle) ? 0.0f: 1.0f;
// Calculate premultiplied alpha values for the cube
c[0] = rgb[0] * alpha;
c[1] = rgb[1] * alpha;
c[2] = rgb[2] * alpha;
c[3] = alpha;
c += 4; // advance our pointer into memory for the next color value
}
}
}
map.data = cubeData;
return map;
}
我將這個方法稍微改造了一下,選擇一個結構體,因為外面要用到length和dimension。蘋果沒有提供rgbToHSV方法的實現,可以用我找到的這個:void rgbToHSV(float *rgb, float *hsv) {
float min, max, delta;
float r = rgb[0], g = rgb[1], b = rgb[2];
float *h = hsv, *s = hsv + 1, *v = hsv + 2;
min = fmin(fmin(r, g), b );
max = fmax(fmax(r, g), b );
*v = max;
delta = max - min;
if( max != 0 )
*s = delta / max;
else {
*s = 0;
*h = -1;
return;
}
if( r == max )
*h = ( g - b ) / delta;
else if( g == max )
*h = 2 + ( b - r ) / delta;
else
*h = 4 + ( r - g ) / delta;
*h *= 60;
if( *h < 0 )
*h += 360;
}
我在.c文件中導入的庫:#include
#include
#include
// ComplexFilters-Bridging-Header.h
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import CubeMap.c
@IBAction func replaceBackground() {
let cubeMap = createCubeMap(60,90)
let data = NSData(bytesNoCopy: cubeMap.data, length: Int(cubeMap.length), freeWhenDone: true)
let colorCubeFilter = CIFilter(name: CIColorCube)
colorCubeFilter.setValue(cubeMap.dimension, forKey: inputCubeDimension)
colorCubeFilter.setValue(data, forKey: inputCubeData)
colorCubeFilter.setValue(CIImage(image: imageView.image), forKey: kCIInputImageKey)
var outputImage = colorCubeFilter.outputImage
let sourceOverCompositingFilter = CIFilter(name: CISourceOverCompositing)
sourceOverCompositingFilter.setValue(outputImage, forKey: kCIInputImageKey)
sourceOverCompositingFilter.setValue(CIImage(image: UIImage(named: background)), forKey: kCIInputBackgroundImageKey)
outputImage = sourceOverCompositingFilter.outputImage
let cgImage = context.createCGImage(outputImage, fromRect: outputImage.extent())
imageView.image = UIImage(CGImage: cgImage)
}
參數設置都還比較簡單,CISourceOverCompositing濾鏡目前已經使用過多次了,並沒有什麼復雜的。編譯、運行,可以分兩次執行,先看消除深綠色的效果,再看最後使用CISourceOverCompositing濾鏡組合圖片之後的效果: