Пользовательский CIColorKernel в Xamarin IOS
Я пытаюсь создать функциональность в формах Xamarin, которая позволяет приложению изменять цвета [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());
2 ответа
Изменить все синие и фиолетовые пиксели на желтые и оранжевые
Давайте разберем это на два отдельных шага:
- синие пиксели в желтые пиксели
- фиолетовые пиксели в оранжевые пиксели
Это означает, что нам нужен только один CIFilter
это меняет один цвет на другой, и мы можем связать два (или более) этих фильтра вместе, чтобы изменить столько цветов, сколько необходимо.
С точки зрения обычая CIFilter
, если мы меняем только цвет, мы можем использовать CIColorKernel
обработать это на графическом или векторном модуле ЦП (ОС определит, какой из них будет зависеть от доступности и запрошенных функций ядра). CIKernel
подклассы используют модифицированную версию GLSL
(OpenGL Shading Language) как язык в ядре (этот код компилируется во время выполнения, так как каждое устройство может иметь разные 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
если вы делаете это CIImage
непосредственно к держателю UIImage, у которого уже есть контекст. 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(s). Таким образом, ядро GLSL должно быть скомпилировано только один раз для каждого фильтра для неограниченного количества входных / выходных изображений.
Вот пример, где изменяются входные цвета фильтров. Фильтры создаются и поддерживаются объектами уровня класса UIViewController и удаляются, когда контроллер представления закрыт.
Другой пример использования порога для интерактивной замены "синих" пикселей на красные.
Re: Core Image Kernel Language Re: CIContext Re: CIColorKernel
Я использовал фильтр в 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);
Это сделано для размытия изображения, но я думаю, что такая же процедура для других фильтров