CIGaussianBlurを画像に適用すると、画像の角がぼやけて元の画像よりも小さく見えることに気づきました。そのため、画像のエッジが透明にならないように、正しくトリミングする必要があることがわかりました。しかし、ぼかしの量に応じてトリミングする必要がある量を計算するにはどうすればよいですか?
例:
元の画像:
CIGaussianBlurのinputRadiusが50の画像(青色はすべての背景です):
CIGaussianBlurを画像に適用すると、画像の角がぼやけて元の画像よりも小さく見えることに気づきました。そのため、画像のエッジが透明にならないように、正しくトリミングする必要があることがわかりました。しかし、ぼかしの量に応じてトリミングする必要がある量を計算するにはどうすればよいですか?
例:
元の画像:
CIGaussianBlurのinputRadiusが50の画像(青色はすべての背景です):
例として次のコードを取り上げます...
CIContext *context = [CIContext contextWithOptions:nil];
CIImage *inputImage = [[CIImage alloc] initWithImage:image];
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
[filter setValue:inputImage forKey:kCIInputImageKey];
[filter setValue:[NSNumber numberWithFloat:5.0f] forKey:@"inputRadius"];
CIImage *result = [filter valueForKey:kCIOutputImageKey];
CGImageRef cgImage = [context createCGImage:result fromRect:[result extent]];
これにより、上記で提供した画像が作成されます。しかし、代わりに元の画像rectを使用して、コンテキストからCGImageを作成すると、結果の画像は目的のサイズになります。
CGImageRef cgImage = [context createCGImage:result fromRect:[inputImage extent]];
2つの問題があります。1つ目は、ブラーフィルターが入力画像のエッジの外側のピクセルをサンプリングすることです。これらのピクセルは透明です。そこから透明なピクセルが生まれます。秘訣は、ぼかしフィルターを適用する前にエッジを拡張することです。これは、例えば次のようなクランプフィルターによって行うことができます。
CIFilter *affineClampFilter = [CIFilter filterWithName:@"CIAffineClamp"];
CGAffineTransform xform = CGAffineTransformMakeScale(1.0, 1.0);
[affineClampFilter setValue:[NSValue valueWithBytes:&xform
objCType:@encode(CGAffineTransform)]
forKey:@"inputTransform"];
このフィルターはエッジを無限に拡張し、透明度を排除します。次のステップは、ブラーフィルターを適用することです。
2番目の問題は少し奇妙です。一部のレンダラーは、ぼかしフィルター用に大きな出力画像を生成します。結果のCIImageの原点を、次のようにオフセットで調整する必要があります。
CGImageRef cgImage = [context createCGImage:outputImage
fromRect:CGRectOffset([inputImage extend],
offset, offset)];
私のiPhoneのソフトウェアレンダラーには、オフセットの3倍のぼかし半径が必要です。同じiPhone上のハードウェアレンダラーは、オフセットをまったく必要としません。メイビーは、入力画像と出力画像のサイズの違いからオフセットを推測することができますが、私は試しませんでした...
ハードエッジのある画像のきれいなぼかしバージョンを取得するには、最初にCIAffineClampをソース画像に適用し、そのエッジを拡張してから、出力画像を生成するときに入力画像の範囲を使用するようにする必要があります。
コードは次のとおりです。
CIContext *context = [CIContext contextWithOptions:nil];
UIImage *image = [UIImage imageNamed:@"Flower"];
CIImage *inputImage = [[CIImage alloc] initWithImage:image];
CIFilter *clampFilter = [CIFilter filterWithName:@"CIAffineClamp"];
[clampFilter setDefaults];
[clampFilter setValue:inputImage forKey:kCIInputImageKey];
CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[blurFilter setValue:clampFilter.outputImage forKey:kCIInputImageKey];
[blurFilter setValue:@10.0f forKey:@"inputRadius"];
CIImage *result = [blurFilter valueForKey:kCIOutputImageKey];
CGImageRef cgImage = [context createCGImage:result fromRect:[inputImage extent]];
UIImage result = [[UIImage alloc] initWithCGImage:cgImage scale:image.scale orientation:UIImageOrientationUp];
CGImageRelease(cgImage);
このコードはiOSでテストされていることに注意してください。OS Xでも同様である必要があります(UIImageの代わりにNSImageを使用します)。
私はいくつかの解決策を見て、ここで共有されているアイデアのいくつかに基づいて、より現代的なものを推奨したいと思いました。
private lazy var coreImageContext = CIContext() // Re-use this.
func blurredImage(image: CIImage, radius: CGFloat) -> CGImage? {
let blurredImage = image
.clampedToExtent()
.applyingFilter(
"CIGaussianBlur",
parameters: [
kCIInputRadiusKey: radius,
]
)
.cropped(to: image.extent)
return coreImageContext.createCGImage(blurredImage, from: blurredImage.extent)
}
後で必要な場合UIImage
は、もちろん次のように取得できます。
let image = UIImage(cgImage: cgImage)
...疑問に思っている人のために、aを返す理由CGImage
は(Appleのドキュメントに記載されているように):
Core Imageの座標系がと一致しないため、このフィルタリングアプローチでは、「contentMode」で
UIKit
表示すると予期しない結果が生じる可能性があります。contentModeを適切に処理できるようにUIImageView
、必ずでバックアップしてください。CGImage
が必要な場合はCIImage
それを返すことができますが、この場合、画像を表示している場合は、おそらく注意が必要です。
これは私のために働きます:)
CIContext *context = [CIContext contextWithOptions:nil];
CIImage *inputImage = [[CIImage alloc] initWithImage:image];
CIFilter *blurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
[blurFilter setDefaults];
[blurFilter setValue:inputImage forKey:@"inputImage"];
CGFloat blurLevel = 20.0f; // Set blur level
[blurFilter setValue:[NSNumber numberWithFloat:blurLevel] forKey:@"inputRadius"]; // set value for blur level
CIImage *outputImage = [blurFilter valueForKey:@"outputImage"];
CGRect rect = inputImage.extent; // Create Rect
rect.origin.x += blurLevel; // and set custom params
rect.origin.y += blurLevel; //
rect.size.height -= blurLevel*2.0f; //
rect.size.width -= blurLevel*2.0f; //
CGImageRef cgImage = [context createCGImage:outputImage fromRect:rect]; // Then apply new rect
imageView.image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
これは、画像をぼかすSwift5バージョンです。クランプフィルターをデフォルトに設定して、変換を行う必要がないようにします。
func applyBlurEffect() -> UIImage? {
let context = CIContext(options: nil)
let imageToBlur = CIImage(image: self)
let clampFilter = CIFilter(name: "CIAffineClamp")!
clampFilter.setDefaults()
clampFilter.setValue(imageToBlur, forKey: kCIInputImageKey)
//The CIAffineClamp filter is setting your extent as infinite, which then confounds your context. Try saving off the pre-clamp extent CGRect, and then supplying that to the context initializer.
let inputImageExtent = imageToBlur!.extent
guard let currentFilter = CIFilter(name: "CIGaussianBlur") else {
return nil
}
currentFilter.setValue(clampFilter.outputImage, forKey: kCIInputImageKey)
currentFilter.setValue(10, forKey: "inputRadius")
guard let output = currentFilter.outputImage, let cgimg = context.createCGImage(output, from: inputImageExtent) else {
return nil
}
return UIImage(cgImage: cgimg)
}
Swiftバージョンは次のとおりです。
func applyBlurEffect(image: UIImage) -> UIImage {
let context = CIContext(options: nil)
let imageToBlur = CIImage(image: image)
let blurfilter = CIFilter(name: "CIGaussianBlur")
blurfilter!.setValue(imageToBlur, forKey: "inputImage")
blurfilter!.setValue(5.0, forKey: "inputRadius")
let resultImage = blurfilter!.valueForKey("outputImage") as! CIImage
let cgImage = context.createCGImage(resultImage, fromRect: resultImage.extent)
let blurredImage = UIImage(CGImage: cgImage)
return blurredImage
}
Xamarin(C#)の以下の2つの実装を参照してください。
public static UIImage Blur(UIImage image)
{
using(var blur = new CIGaussianBlur())
{
blur.Image = new CIImage(image);
blur.Radius = 6.5f;
using(CIImage output = blur.OutputImage)
using(CIContext context = CIContext.FromOptions(null))
using(CGImage cgimage = context.CreateCGImage (output, new RectangleF(0, 0, image.Size.Width, image.Size.Height)))
{
return UIImage.FromImage(cgimage);
}
}
}
上記の方法を使用すると、iOS 7では正しく機能しなくなります(少なくとも現時点ではXamarin 7.0.1では)。そこで、別の方法でトリミングを追加することにしました(測定値はぼかし半径によって異なる場合があります)。
private static UIImage BlurImage(UIImage image)
{
using(var blur = new CIGaussianBlur())
{
blur.Image = new CIImage(image);
blur.Radius = 6.5f;
using(CIImage output = blur.OutputImage)
using(CIContext context = CIContext.FromOptions(null))
using(CGImage cgimage = context.CreateCGImage (output, new RectangleF(0, 0, image.Size.Width, image.Size.Height)))
{
return UIImage.FromImage(Crop(CIImage.FromCGImage(cgimage), image.Size.Width, image.Size.Height));
}
}
}
private static CIImage Crop(CIImage image, float width, float height)
{
var crop = new CICrop
{
Image = image,
Rectangle = new CIVector(10, 10, width - 20, height - 20)
};
return crop.OutputImage;
}
これを試して、入力の範囲を-createCGImage:fromRect:
のパラメータとします。
-(UIImage *)gaussianBlurImageWithRadius:(CGFloat)radius {
CIContext *context = [CIContext contextWithOptions:nil];
CIImage *input = [CIImage imageWithCGImage:self.CGImage];
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
[filter setValue:input forKey:kCIInputImageKey];
[filter setValue:@(radius) forKey:kCIInputRadiusKey];
CIImage *output = [filter valueForKey:kCIOutputImageKey];
CGImageRef imgRef = [context createCGImage:output
fromRect:input.extent];
UIImage *outImage = [UIImage imageWithCGImage:imgRef
scale:UIScreen.mainScreen.scale
orientation:UIImageOrientationUp];
CGImageRelease(imgRef);
return outImage;
}