【问题标题】:Custom CIColorKernel in Xamarin IOSXamarin IOS 中的自定义 CIColorKernel
【发布时间】:2018-04-29 15:37:12
【问题描述】:

我正在尝试在 Xamarin Forms 中创建一个功能,允许应用程序将 [1..N] 颜色更改为图像的 [1..N]。

示例:

将所有蓝色和紫色像素更改为黄色和橙色像素

经过一番调查,似乎我需要创建一个自定义的 CIColorKernel 来实现它。

问题是很难找到示例并且文档很简单。

如果有人要开始使用教程或基本示例...

谢谢

编辑:

我实现了@SushiHangover的soutioion,并在代码示例的第二种方法中调用它:

 private IImageSourceHandler GetHandler(ImageSource source)
        {
            IImageSourceHandler returnValue = null;
            if (source is UriImageSource)
            {
                returnValue = new ImageLoaderSourceHandler();
            }
            else if (source is FileImageSource)
            {
                returnValue = new FileImageSourceHandler();
            }
            else if (source is StreamImageSource)
            {
                returnValue = new StreamImagesourceHandler();
            }
            return returnValue;
        }

        public async Task<ImageSource> ChangeImageColor(ImageSource source, string oldColor, string newColor)
        {
            var handler = GetHandler(source);
            var uiImage = (UIImage)null;

            uiImage = await handler.LoadImageAsync(source);
            UIImage uiImageOutput = null;

            using (var context = new EAGLContext(EAGLRenderingAPI.OpenGLES3))
            using (var filter = new ColorReplaceFilter
            {
                InputImage = new CIImage(uiImage),
                MatchColor = CIColor.FromRgb(200, 200, 200),
                ReplaceWithColor = CIColor.RedColor,
                Threshold = 1f // Exact match, values >0 & <=1 to make a fuzzy match
            })
            {
                uiImageOutput = UIImage.FromImage(filter.OutputImage);
            }

            return Xamarin.Forms.ImageSource.FromStream(() => uiImageOutput.AsPNG().AsStream()); ;
        }

这两个方法位于一个名为 BitmapHelper 的类中,该类在 Xamarin Forms 项目中使用 Dependecy Injection 调用。

var bitmap = DependencyService.Get<IBitmapHelper>().ChangeImageColor(AmbiancePicture.Source, oldColor, newColor);
            AmbiancePicture.Source = bitmap.Result;

结果包含预期的新图像,但 AmbiancePicture.Source 但未更新。

这是我尝试更改的图像:

编辑 2:

如果我在更新前将 AmbiancePicture.Source 设置为 null,则图像保持为空。 图像似乎不是空的(我在流中看到了一些正确的属性)。

工作编辑:

所以错误来自UIImage创建和转换之后。

这是工作代码:

  using (var context = new EAGLContext(EAGLRenderingAPI.OpenGLES3))
            using (var filter = new ColorReplaceFilter
            {
                InputImage = new CIImage(uiImage),
                MatchColor = CIColor.FromString(oldColor),
                ReplaceWithColor = CIColor.FromString(newColor),
                Threshold = 1f // Exact match, values >0 & <=1 to make a fuzzy match
            })
           {
            var output = context.CreateCGImage(filter.OutputImage, filter.OutputImage.Extent); // This line is slow...
            var img = UIImage.FromImage(output);
            jpegData = img.AsJPEG(1.0f);

        }
        return Xamarin.Forms.ImageSource.FromStream(() => jpegData.AsStream());

【问题讨论】:

    标签: ios xamarin xamarin.ios cifilter cikernel


    【解决方案1】:

    将所有蓝色和紫色像素更改为黄色和橙色像素

    让我们将其分解为两个不同的步骤:

    • 蓝色像素到黄色像素
    • 紫色像素到橙色像素

    这意味着我们只需要一个 CIFilter 将一种颜色更改为另一种颜色,我们可以将这些过滤器中的两个(或更多)链接在一起以根据需要更改尽可能多的颜色。

    就自定义CIFilter而言,如果我们只改变颜色,我们可以使用CIColorKernel在GPU或CPU矢量单元上进行处理(操作系统将根据可用性和内核确定哪一个)要求的功能)。 CIKernel 子类使用 GLSL(OpenGL 着色语言)的修改版本作为内核中的语言(此代码在运行时编译,因为每个设备可能有不同的 CPU 和/或 GPU)。

    所以我们需要一个CIColorKernel 函数,它接受RGA8 格式的源“颜色”作为vec4,一个vec4 表示要匹配的颜色,另一个vec4 表示要更改为if 的颜色源 vec4 匹配。我们还可以提供一个阈值,提供原始颜色需要“接近”的程度(如色度键控)。综合所有这些并编写一些 GLSL,我们得到:

    kernel vec4 main(__sample s, __color o, __color r, float threshold) {
        vec4 diff = s.rgba - o;
        float distance = length( diff );
        float alpha = compare( distance - threshold, 0.0, 1.0 );
         if (alpha == 0.0)
            return r;
         return s;
    }
    

    现在我们需要一个CIFilter 子类来创建/编译该内核并向该内核提供Core Image输入和输出:

    public class ColorReplaceFilter : CIFilter
    {
        const string filterName = "colorReplace";
        const int numArgs = 4;
        const string coreImageShaderProgram =
            @"
                kernel vec4 main(__sample s, __color o, __color r, float threshold) {
                    vec4 diff = s.rgba - o;
                    float distance = length( diff );
                    float alpha = compare( distance - threshold, 0.0, 1.0 );
                     if (alpha == 0.0)
                        return r;
                     return s;
                }
            ";
    
        NSObject[] arguments;
        CIColorKernel colorKernel;
    
        public ColorReplaceFilter() { Initializer(); }
        public ColorReplaceFilter(NSCoder coder) : base(coder) { Initializer(); }
        public ColorReplaceFilter(NSObjectFlag t) : base(t) { Initializer(); }
        public ColorReplaceFilter(IntPtr handle) : base(handle) { Initializer(); }
    
        public CIImage InputImage { get; set; }
        public CIColor MatchColor { get; set; }
        public CIColor ReplaceWithColor { get; set; }
    
        NSNumber _threshold;
        public nfloat Threshold
        {
            get { return _threshold.NFloatValue; }
            set { _threshold = NSNumber.FromNFloat(value); }
        }
    
        void Initializer()
        {
            arguments = new NSObject[numArgs];
            colorKernel = CIColorKernel.FromProgramSingle(coreImageShaderProgram);
            MatchColor = CIColor.WhiteColor;
            ReplaceWithColor = CIColor.WhiteColor;
            _threshold = new NSNumber(0.2f);
        }
    
        public override string Name
        {
            get => filterName;
        }
    
        public override CIImage OutputImage
        {
            get => CreateOutputImage();
        }
    
        CIImage CreateOutputImage()
        {
            if (InputImage != null) // Avoid object creation to allow fast filter chaining
            {
                arguments[0] = InputImage as NSObject;
                arguments[1] = MatchColor as NSObject;
                arguments[2] = ReplaceWithColor as NSObject;
                arguments[3] = _threshold as NSObject;
                var ciImage = colorKernel.ApplyWithExtent(InputImage.Extent, arguments);
                return ciImage;
            }
            return null;
        }
    
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            arguments = null;
            InputImage = null;
            MatchColor = null;
            ReplaceWithColor = null;
            colorKernel.Dispose();
        }
    }
    

    一个简单的CIContext 示例:

    var uiImageInput = inputVIew.Image;
    UIImage uiImageOutput;
    using (var context = CIContext.Create())
    using (var filter = new ColorReplaceFilter
    {
        InputImage = new CIImage(uiImageInput),
        MatchColor = CIColor.BlueColor,
        ReplaceWithColor = CIColor.RedColor,
        Threshold = 0.0f // Exact match, values >0 & <=1 to make a fuzzy match
    })
    {
        uiImageOutput  = UIImage.FromImage(filter.OutputImage);
    }
    // Do something with your new uiImageOutput
    

    注意:如果您对实时图像/视频处理有特殊需求,也可以使用基于 EAGLContext 的上下文。

    注意:由于创建上下文有开销,如果您将CIImage 直接呈现给已经有上下文的 UIImage 持有者,您确实不需要需要CIContextUIImageView 将有一个上下文,因为它在屏幕上显示图像,因此删除上下文创建并将支持 UIImageCIImage 直接创建/分配给 UIImageView

    aUIImageViewObject.Image = UIImage.FromImage(replaceFilter.OutputImage);
    

    在链接过滤器中,您获取一个过滤器的CIImage 输出并将其作为CIImage 输入应用到下一个过滤器。

    var uiImageInput = inputVIew.Image;
    UIImage uiImageOutput;
    using (var context = CIContext.Create())
    using (var filter1 = new ColorReplaceFilter
    {
        InputImage = new CIImage(uiImageInput),
        MatchColor = CIColor.BlueColor,
        ReplaceWithColor = CIColor.RedColor,
        Threshold = 0
    })
    using (var filter2 = new ColorReplaceFilter
    {
        InputImage = filter1.OutputImage,
        MatchColor = CIColor.WhiteColor,
        ReplaceWithColor = CIColor.BlackColor,
        Threshold = 0
    })
    {
        uiImageOutput = UIImage.FromImage(filter2.OutputImage);
    }
    // Do something with your new UIImage
    

    正如 Apple 所说,a CIImage object is a “recipe” for producing an image 并且由于我们直到上面的链结束时才“渲染” UIImage|CGImage,所以这种链式过滤器渲染发生在一次通过 因为我们只是通过链传递CIImages。

    注意:如果您正在处理具有不同颜色替换的多个图像,请创建一次 CIFilters 并不断更改 CIImage 输入和替换颜色以重复使用过滤器来处理每个图像。完成后请记住处理 CIFilter(s)。这样,对于无限数量的输入/输出图像,每个过滤器只需编译一次 GLSL 内核。

    这是一个改变过滤器的输入颜色的例子。过滤器创建和维护 UIViewController 的类级别对象,并在视图控制器关闭时处理。

    另一个使用阈值以交互方式将“蓝色”像素替换为红色的示例。

    回复:Core Image Kernel Language 回复:CIContext 回复:CIColorKernel

    【讨论】:

    • 非常感谢您非常完整的解释,我今天学到了新东西^^。我在我的 XamarinForms 应用程序中实现了它,它的计算速度非常快,但我的图像没有改变......我想我错过了将 UIImage 转换为 XForms 图像的一些东西。我用我的一些代码编辑我的帖子
    • @OrcusZ 包括 alpha 通道:CIColor.FromRgba(200, 200, 200, 255)imgur.com/a/3pmvG
    • 正如我所料,这不是您的示例,而是我创建 UIImage 并将其转换为 Xamarin Form 图像的方式。我会更新我的帖子。非常感谢您的帮助
    • @OrcusZ 您正在将 CIImage 带到 UIImage,然后到 Jpeg,然后到字节流,然后到 ImageSource,我必须假设 ImageSource 被尝试到 Forms' Image 所以那就是将其作为基于 CGImage 的 UImage 带回 UIImage。我不知道您是如何使用它的,但是您需要从该过程中完全删除这些转换和表单,并使用预先存在的上下文制作由 CIImage 过滤器输出支持的 UIImage。再说一次,我不知道你在用它做什么,但是像这样简单的过滤器可以处理 iOS 的 60hz 屏幕刷新并改变实时视频。
    • @OrcusZ 有多种显示 CIImage 的方法,您当然可以使用 UIImageView(Form 使用的,因此您可以继承 Form 的 iOS Image 渲染器并直接在其中嵌入过滤器),也您可以编写自己的表单控件并使用 GLKView(基于 OpenGL)或 MTKView(基于金属),这实际上取决于您的应用程序的要求,但通常您需要远离任何转换,绝对是任何托管对象的创建和尽可能多的托管代码(尤其是表单)
    【解决方案2】:

    我在 Xamarin.iOS 中使用了过滤器,这是一个小示例代码:

    CIContext ci = CIContext.Create();
    CGImage img = <YourUIImageInstanceClass>.CGImage;
    CIImage inputImage = CIImage.FromCGImage(img);
    
    CLFGaussianBlur blurFilter = new CLFGaussianBlur();
    
    NSMutableDictionary  inputParameters = new NSMutableDictionary ();
    NSNumber num = new NSNumber(<radius>);
    inputParameters.SetValueForKey(num, new NSString("inputRadius"));
    
    outputImage = inputImage.CreateByFiltering("CIGaussianBlur",     inputParameters);
    

    这是为模糊图像而制作的,但我认为其他滤镜的过程相同

    【讨论】:

    • 不考虑第 5 行
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-16
    • 1970-01-01
    • 2018-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多