Xamarin 中的自定义 CIColorKernel IOS

Custom CIColorKernel in Xamarin IOS

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

示例:

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

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

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

如果有人有教程或基本示例可以开始...

谢谢

编辑:

我实现了@SushiHangover 的解决方案,并在代码示例的第二个方法中调用了它:

 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 的 class 中,在 Xamarin Forms 项目中使用 Dependecy Injection[=62] 调用=].

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

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

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

Change all blue & purple pixels to yellow & orange pixels

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

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

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

就自定义 CIFilter 而言,如果我们仅更改颜色,我们可以使用 CIColorKernel 在 GPU 或 CPU 向量单元( OS 将根据可用性和请求的内核功能确定哪一个)。 CIKernel subclasses 使用 GLSL(OpenGL 着色语言)的修改版本作为内核中的语言(此代码在运行时编译,因为每个设备可能有不同的 CPU and/or GPU).

所以我们需要一个 CIColorKernel 函数,它接受 RGA8 格式的源 "color" 作为 vec4,一个 vec4 表示要匹配的颜色,另一个 vec4 表示如果源 vec4 匹配则更改为的颜色。我们还可以提供一个阈值,该阈值提供 "close" 原始颜色需要的方式(如色度键控)。综上所述并编写一些 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 subclass 到 create/compile 该内核,并向该内核提供 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

注意:如果您对实时 image/video 处理有特殊需求,也有基于 EAGLContext 的上下文。

注意:由于创建上下文会产生开销,因此如果要将 CIImage 直接渲染到 UIImage 持有者,则 不需要 CIContext已经有了上下文。 UIImageView 将有一个上下文,因为它在屏幕上显示图像,因此删除上下文创建和 create/assign CIImage 支持 UIImage 直接到 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 并且由于我们 "render" 一个 UIImage|CGImage 直到上面链的末尾,这个链式滤镜渲染发生在 一次通过,因为我们只通过链 CIImages。

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

这是一个过滤器的输入颜色被改变的例子。过滤器被创建并维护为 UIViewController 的 class 级对象,并在视图控制器关闭时被释放。

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

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