はじめに
人物の背景を切り出して合成したい場合など、
SF映画ではクロマキー合成を使って表現されています。
iOSのCIFilterにも似たようなものがないか探したのですが、
見当たらなかったので、簡単に作れる範囲で作成してみました。
クロマキー合成のhowtoには諸説あるようなので、あまり難しい事は抜きで
画像データをピクセル走査するサンプルとして考えて頂ければと思います。
方法
タッチした場所の<色A>を取得
↓
<色A> = 白 / それ以外 = 黒 の<二値化画像B>を生成
↓
元画像と<二値化画像B>を合成して、差分の黒色のみ切り抜きを行う
↓
背景画像と合成
1:まずタッチした場所の色(UIColor)を取得するコードを描きます。
UIImageのextensionとして作成します。
<ポイント>
RGBA各1byteなので1pixelは4byte
(var pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4)
extension UIImage {
func getPixelColor(pos: CGPoint) -> UIColor {
var pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.CGImage))
var data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
var pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4
var r = CGFloat(data[pixelInfo]) / CGFloat(255.0)
var g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
var b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
var a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
return UIColor(red: r, green: g, blue: b, alpha: a)
}
}
2.次に、UIImageの縦横を1pixずつ全走査して、対象の色(1で取得したColor)であれば白、違えば黒の
UIImageを返すようなextensionを書きます。
<ポイント>
画像の幅、高さの分だけforでまわして、point = (x, y)の色を取得する
RGBA各1byteなので1pixelは4byte(let newByteLength = _width * _height * 4)
CGImageCreate(RGBバイト配列から、UIImageを作る)を使って、UIImageを返す
//入力した色と同じ色を白に塗りつぶす
func getMaskImageFromTappedColor(_tColor:UIColor) -> UIImage? {
var _image = self
var _width = Int(_image.size.width)
var _height = Int(_image.size.height)
var _imageData = _image.imageData()
var imageBytes : UnsafeMutablePointer<Byte>;
let newByteLength = _width * _height * 4
imageBytes = UnsafeMutablePointer<Byte>.alloc(newByteLength)
var _cnt = 0;
for x in 0..<_width {
for y in 0..<_height {
var point = (x, y)
var color = UIImage.colorAtPoint(
point,
imageWidth: _width,
withData: _imageData
)
var i: Int = ((Int(_width) * Int(y)) + Int(x)) * 4
if(color == _tColor){
imageBytes[i] = Byte(255) // red
imageBytes[i+1] = Byte(255); // green
imageBytes[i+2] = Byte(255); // blue
imageBytes[i+3] = Byte(255); // alpha
}else{
imageBytes[i] = Byte(0) // red
imageBytes[i+1] = Byte(0); // green
imageBytes[i+2] = Byte(0); // blue
imageBytes[i+3] = Byte(255); // alpha
}
}
}
var provider = CGDataProviderCreateWithData(nil,imageBytes, UInt(newByteLength), nil)
var bitsPerComponent:UInt = 8
var bitsPerPixel:UInt = bitsPerComponent * 4
var bytesPerRow:UInt = UInt(4) * UInt(_width)
var colorSpaceRef = CGColorSpaceCreateDeviceRGB()
var bitmapInfo = CGBitmapInfo.ByteOrderDefault
var renderingIntent = kCGRenderingIntentDefault
var cgImage = CGImageCreate(UInt(_width), UInt(_height), bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, nil, false, renderingIntent)
return UIImage(CGImage: cgImage)
}
3.二値化された画像から、元画像を透過する関数をextensionに作る
func getMaskedImage(maskImage:UIImage!) -> UIImage {
let maskImageReference:CoreImage.CGImage? = maskImage?.CGImage
let mask = CGImageMaskCreate(CGImageGetWidth(maskImageReference),
CGImageGetHeight(maskImageReference),
CGImageGetBitsPerComponent(maskImageReference),
CGImageGetBitsPerPixel(maskImageReference),
CGImageGetBytesPerRow(maskImageReference),
CGImageGetDataProvider(maskImageReference),nil,false)
let maskedImageReference = CGImageCreateWithMask(self.CGImage, mask)
let maskedImage = UIImage(CGImage: maskedImageReference)
return maskedImage!
}
4.最後にUIViewにtoucheBeganメソッドを加えて、タッチした座標をextensionに渡す処理を書く
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
let t = touches.anyObject() as UITouch
//指定したのImageViewの中での座標を取得する
let point = t.locationInView(self.shipImageView)
//get color you touched
var color : UIColor! = self.shipImageView?.image!.getPixelColor(point)
var filteredImage = self.shipImageView?.image!.getFilteredImage(color)
self.shipImageView?.image = filteredImage
}
完成
タッチした場所の色を背景として透過するサンプルが作成されました。
おしまい
今回はタッチした場所の色を抜く事で背景を切り取りましたが、
ニ値化画像の作り方次第で、色々な切り抜き方が可能だと思います。
例えば画像の明るさという観点で、しきい値を作成し、二値化画像を作る事や、
iOSの顔認識で顔の部分だけ丸く切り抜くような二値化画像を作るなどなど
色々と楽しめそうです!