我试图在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
的类中,在 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());
答案 0 :(得分:2)
更改所有蓝色&amp;紫色像素为黄色&amp;橙色像素
让我们将其分解为两个不同的步骤:
这意味着我们只需要一个CIFilter
将一种颜色更改为另一种颜色,我们可以将两种(或更多种)颜色链接在一起,以根据需要更改颜色。
就自定义CIFilter
而言,如果我们只更改颜色,我们可以使用CIColorKernel
在GPU或CPU矢量单元上处理此问题(操作系统将确定哪一个关于可用性和内核功能请求)。 CIKernel
子类使用修改版本的GLSL
(OpenGL着色语言)作为内核中的语言(此代码在运行时编译,因为每个设备可能具有不同的CPU和/或GPU)。
所以我们需要一个CIColorKernel
函数,它接受RGA8格式的源“颜色”为vec4
,vec4
表示要匹配的颜色,另一个vec4
如果源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
的上下文。
注意:由于创建上下文有开销,如果要将CIContext
直接呈现给已有上下文的UIImage持有者,则不需要CIImage
。 UIImageView
会有一个上下文,因为它在屏幕上显示图像,因此删除上下文创建并直接创建/分配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
并且由于我们直到上面的链末尾才“渲染”UIImage | CGImage,因此这个链式过滤器渲染发生在 一次传递 因为我们只是将CIImage
传递给了链。
注意:如果要处理多个具有不同颜色替换的图像,请创建一次CIF过滤器并继续更改CIImage输入和替换颜色以重复使用过滤器处理每个图像。完成后记得丢弃CIFilter。这样,每个过滤器只需要为无限数量的输入/输出图像编译一次GLSL内核。
以下是更改过滤器输入颜色的示例。过滤器创建并维护UIViewController的类级对象,并在视图控制器关闭时处理。
使用阈值以红色交互式替换“蓝色”像素的另一个示例。
答案 1 :(得分:0)
我在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);
这是为了模糊图像,但我认为与其他过滤器的过程相同