【问题标题】:Correct crop of CIGaussianBlurCIGaussianBlur 的正确裁剪
【发布时间】:2012-10-02 03:03:46
【问题描述】:

我注意到当 CIGaussianBlur 应用于图像时,图像的边角变得模糊,因此看起来比原始图像要小。所以我发现我需要正确裁剪它以避免图像的透明边缘。但是如何根据模糊量计算我需要裁剪多少?


例子:

原图:

CIGaussianBlur 输入半径为 50 的图像(蓝色是一切的背景):

【问题讨论】:

  • 只是顺便说一下这只狮子超级坏蛋。

标签: objective-c macos core-image


【解决方案1】:

以下面的代码为例……

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]];

【讨论】:

  • 为了达到想要的效果,您需要先应用 Clamp 滤镜,然后再进行模糊。查看@orj 提供的解决方案
【解决方案2】:

有两个问题。首先是模糊过滤器对输入图像边缘之外的像素进行采样。这些像素是透明的。这就是透明像素的来源。 诀窍是在应用模糊滤镜之前扩展边缘。这可以通过钳位滤波器来完成,例如像这样:

CIFilter *affineClampFilter = [CIFilter filterWithName:@"CIAffineClamp"];

CGAffineTransform xform = CGAffineTransformMakeScale(1.0, 1.0);
[affineClampFilter setValue:[NSValue valueWithBytes:&xform
                                           objCType:@encode(CGAffineTransform)]
                     forKey:@"inputTransform"];

此过滤器无限扩展边缘并消除透明度。下一步是应用模糊滤镜。

第二个问题有点奇怪。一些渲染器为模糊过滤器生成更大的输出图像,您必须通过一些偏移量来调整生成的 CIImage 的原点,例如像这样:

CGImageRef cgImage = [context createCGImage:outputImage
                                   fromRect:CGRectOffset([inputImage extend],
                                                         offset, offset)];

我 iPhone 上的软件渲染器需要三倍的模糊半径作为偏移量。同一部 iPhone 上的硬件渲染器根本不需要任何偏移。也许你可以从输入和输出图像的大小差异中推断出偏移量,但我没有尝试......

【讨论】:

  • 这非常有效。我只是想指出,在您的示例中分配的 CGAffineTransform 是过滤器的默认值,因此您可以省略所有代码,或者只是调用 [affineClampFilter setDefaults]
【解决方案3】:

要获得具有硬边缘的图像的漂亮模糊版本,您首先需要将 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 类似(用 NSImage 代替 UIImage)。

【讨论】:

    【解决方案4】:

    我看到了一些解决方案,并想根据此处分享的一些想法推荐一个更现代的解决方案:

    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)
    

    ... 对于那些想知道的人,返回 CGImage 的原因是(如 Apple documentation 中所述):

    由于 Core Image 的坐标系与 UIKit 不匹配,当在带有“contentMode”的UIImageView 中显示时,这种过滤方法可能会产生意想不到的结果。请务必使用 CGImage 支持它,以便它正确处理 contentMode。

    如果您需要 CIImage,您可以返回它,但在这种情况下,如果您要显示图像,您可能需要小心。

    【讨论】:

    • 谢谢本,这是最好的答案
    【解决方案5】:

    这对我有用:)

    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);
    

    【讨论】:

      【解决方案6】:

      这是模糊图像的 Swift 5 版本。将 Clamp 过滤器设置为默认值,这样您就无需进行转换。

      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)
      
      }
      

      【讨论】:

      • extent 来自哪里?我正在当前项目中尝试此解决方案,我用于输入的图像大小为 60.33 x 70.0,但生成的范围为 149 x 188,这是不同的纵横比。也许这与我使用带有方形框架的UIImageView(使用宽高比)这一事实有关,但据我所知,该函数对UIImageView 一无所知。为什么这些纵横比不同?我希望它们是相同的,也许只是从模糊的边缘延伸稍微更方形。不过,模糊的图像不那么方形。
      【解决方案7】:

      这里是 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
      
      }
      

      【讨论】:

        【解决方案8】:

        请参阅以下两种 Xamarin (C#) 实现。

        1) 适用于 iOS 6

        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);
                }
            }
        }
        

        2) iOS 7 的实现

        使用上面显示的方式不再在 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;   
        }
        

        【讨论】:

          【解决方案9】:

          试试这个,让输入的范围是-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;
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-12-25
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-06-10
            • 2013-08-02
            相关资源
            最近更新 更多