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 直到上面链的末尾,这个链式滤镜渲染发生在 一次通过,因为我们只通过链 CIImage
s。
注意:如果您正在处理大量具有不同颜色替换的图像,请创建一次 CIFilters 并不断更改 CIImage 输入和替换颜色,以重复使用滤镜处理每个图像。完成后记得处理 CIFilter(s)。这样 GLSL 内核只需要为无限数量的 input/output 个图像每个过滤器编译一次。
这是一个过滤器的输入颜色被改变的例子。过滤器被创建并维护为 UIViewController 的 class 级对象,并在视图控制器关闭时被释放。
另一个使用阈值以交互方式将 "blue" 像素替换为红色的示例。
我正在尝试在 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 直到上面链的末尾,这个链式滤镜渲染发生在 一次通过,因为我们只通过链 CIImage
s。
注意:如果您正在处理大量具有不同颜色替换的图像,请创建一次 CIFilters 并不断更改 CIImage 输入和替换颜色,以重复使用滤镜处理每个图像。完成后记得处理 CIFilter(s)。这样 GLSL 内核只需要为无限数量的 input/output 个图像每个过滤器编译一次。
这是一个过滤器的输入颜色被改变的例子。过滤器被创建并维护为 UIViewController 的 class 级对象,并在视图控制器关闭时被释放。
另一个使用阈值以交互方式将 "blue" 像素替换为红色的示例。