1 /*****************************************************************************
2 * ci_filters.m: Video filters for MacOSX OpenGL video output
3 *****************************************************************************
4 * Copyright © 2017 VLC authors, VideoLAN and VideoLabs
6 * Author: Victorien Le Couviour--Tuffet <victorien.lecouviour.tuffet@gmail.com>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
28 #include <stdatomic.h>
30 #include <vlc_common.h>
31 #include <vlc_filter.h>
32 #include <vlc_picture.h>
33 #include <vlc_plugin.h>
34 #include <vlc_modules.h>
35 #include <vlc_mouse.h>
36 #include "filter_picture.h"
39 #include <CoreImage/CIContext.h>
40 #include <CoreImage/CIImage.h>
41 #include <CoreImage/CIFilter.h>
42 #include <CoreImage/CIVector.h>
48 FILTER_ADJUST_COLOR_CONTROLS,
57 NUM_MAX_EQUIVALENT_VLC_FILTERS = 3
60 #define NUM_FILTER_PARAM_MAX 4
64 enum filter_type filter;
66 _Atomic float ci_params[NUM_FILTER_PARAM_MAX];
67 struct filter_chain * next;
71 #define PSYCHEDELIC_COUNT_DEFAULT 5
72 #define PSYCHEDELIC_COUNT_MIN 3
73 #define PSYCHEDELIC_COUNT_MAX 40
82 CVPixelBufferPoolRef cvpx_pool;
83 video_format_t cvpx_pool_fmt;
85 struct filter_chain * fchain;
86 filter_t * src_converter;
87 filter_t * dst_converter;
90 typedef struct filter_sys_t
92 char const * psz_filter;
94 vlc_mouse_t old_mouse;
96 struct ci_filters_ctx * ctx;
113 struct filter_param_desc
123 } const param_descs[NUM_FILTER_PARAM_MAX];
124 void (*pf_init)(filter_t *filter, struct filter_chain *fchain);
125 void (*pf_control)(filter_t *filter, struct filter_chain *fchain);
128 static void filter_PsychedelicInit(filter_t *filter, struct filter_chain *fchain);
129 static void filter_PsychedelicControl(filter_t *filter, struct filter_chain *fchain);
131 static struct filter_desc filter_desc_table[] =
133 [FILTER_ADJUST_HUE] =
135 { "adjust", @"CIHueAdjust" },
137 { "hue", @"inputAngle", {{{-180.f, +180.f}, {+3.f, -3.f}}}, VLC_VAR_FLOAT }
140 [FILTER_ADJUST_COLOR_CONTROLS] =
142 { "adjust", @"CIColorControls" },
144 { "contrast", @"inputContrast", {{{.0f, 2.f}, {.0f, 2.f}}}, VLC_VAR_FLOAT },
145 { "brightness", @"inputBrightness", {{{.0f, 2.f}, {-1.f, +1.f}}}, VLC_VAR_FLOAT },
146 { "saturation", @"inputSaturation", {{{.0f, 3.f}, {.0f, 2.7f}}}, VLC_VAR_FLOAT }
149 [FILTER_ADJUST_GAMMA] =
151 { "adjust", @"CIGammaAdjust" },
153 { "gamma", @"inputPower", {{{.01f, 1.f}, {10.f, 1.f}}, {{1.f, 10.f}, {1.f, .01f}}}, VLC_VAR_FLOAT }
158 { "invert", @"CIColorInvert" }
162 { "posterize", @"CIColorPosterize" },
164 { "posterize-level", @"inputLevels", {{{2.f, 256.f}, {2.f, 256.f}}}, VLC_VAR_INTEGER }
169 { "sepia", @"CISepiaTone" },
171 { "sepia-intensity", @"inputIntensity", {{{.0f, 255.f}, {.0f, 1.f}}}, VLC_VAR_INTEGER }
176 { "sharpen", @"CISharpenLuminance" },
178 { "sharpen-sigma", @"inputSharpness", {{{.0f, 2.f}, {.0f, 5.f}}}, VLC_VAR_FLOAT }
181 [FILTER_PSYCHEDELIC] =
183 { "psychedelic", @"CIKaleidoscope" }, { { } },
184 filter_PsychedelicInit,
185 filter_PsychedelicControl
189 { "custom" }, { { } },
190 filter_PsychedelicInit,
191 filter_PsychedelicControl
195 #define GET_CI_VALUE(vlc_value, vlc_range, ci_range) \
196 ((vlc_value - vlc_range.min) * (ci_range.max - ci_range.min) / \
197 (vlc_range.max - vlc_range.min) + ci_range.min)
199 static struct filter_chain *
200 filter_chain_AddFilter(struct filter_chain **fchain, enum filter_type filter)
202 struct filter_chain *elem = calloc(1, sizeof(*elem));
205 elem->filter = filter;
211 struct filter_chain *it = *fchain;
212 while (it->next) it = it->next;
220 filter_chain_RemoveFilter(struct filter_chain **fchain,
221 enum filter_type filter)
223 struct filter_chain *prev = NULL;
224 struct filter_chain *to_del;
226 for (to_del = *fchain; to_del && to_del->filter != filter;
227 to_del = to_del->next)
231 *fchain = to_del->next;
233 prev->next = to_del->next;
239 filter_desc_table_GetFilterTypes
240 (char const *vlc_filter_name,
241 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS])
244 for (int i = 0; i < NUM_FILTERS; ++i)
245 if (!strcmp(filter_desc_table[i].name_desc.vlc, vlc_filter_name))
247 assert(j < NUM_MAX_EQUIVALENT_VLC_FILTERS);
248 filter_types[j++] = i;
251 while (j < NUM_MAX_EQUIVALENT_VLC_FILTERS)
252 filter_types[j++] = FILTER_NONE;
255 static inline NSString *
256 filter_desc_table_GetFilterName(enum filter_type type)
257 { assert(type < NUM_FILTERS);
258 return filter_desc_table[type].name_desc.ci;
262 filter_ConvertParam(float f_vlc_val,
263 struct filter_param_desc const *param_desc)
265 struct range clip_range = { param_desc->ranges[0].vlc.min,
266 param_desc->ranges[1].vlc.max
267 ? param_desc->ranges[1].vlc.max
268 : param_desc->ranges[0].vlc.max };
269 f_vlc_val = VLC_CLIP(f_vlc_val, clip_range.min, clip_range.max);
271 unsigned int range_idx;
272 for (range_idx = 0; range_idx < 2; ++range_idx)
273 if (f_vlc_val >= param_desc->ranges[range_idx].vlc.min &&
274 f_vlc_val <= param_desc->ranges[range_idx].vlc.max)
276 assert(range_idx < 2);
278 return GET_CI_VALUE(f_vlc_val,
279 param_desc->ranges[range_idx].vlc,
280 param_desc->ranges[range_idx].ci);
284 ParamsCallback(vlc_object_t *obj,
286 vlc_value_t oldval, vlc_value_t newval,
289 VLC_UNUSED(obj); VLC_UNUSED(oldval);
290 struct filter_chain *filter = p_data;
291 struct filter_param_desc const *filter_param_descs =
292 filter_desc_table[filter->filter].param_descs;
295 while (i < NUM_FILTER_PARAM_MAX &&
296 strcmp(filter_param_descs[i].vlc, psz_var))
298 assert(i < NUM_FILTER_PARAM_MAX);
301 if (filter_param_descs[i].vlc_type == VLC_VAR_FLOAT)
302 new_vlc_val = newval.f_float;
303 else if (filter_param_descs[i].vlc_type == VLC_VAR_INTEGER)
304 new_vlc_val = newval.i_int;
306 vlc_assert_unreachable();
308 atomic_store(filter->ci_params + i,
309 filter_ConvertParam(new_vlc_val, filter_param_descs + i));
314 static void filter_PsychedelicInit(filter_t *filter, struct filter_chain *fchain)
316 filter_sys_t *sys = filter->p_sys;
317 fchain->ctx.psychedelic.x = filter->fmt_in.video.i_width / 2;
318 fchain->ctx.psychedelic.y = filter->fmt_in.video.i_height / 2;
319 fchain->ctx.psychedelic.count = PSYCHEDELIC_COUNT_DEFAULT;
322 static void filter_PsychedelicControl(filter_t *filter, struct filter_chain *fchain)
324 filter_sys_t *sys = filter->p_sys;
326 if (sys->mouse_moved)
328 fchain->ctx.psychedelic.x = sys->mouse.i_x;
329 fchain->ctx.psychedelic.y = filter->fmt_in.video.i_height
330 - sys->mouse.i_y - 1;
332 if (sys->mouse.i_pressed)
334 fchain->ctx.psychedelic.count++;
335 if (fchain->ctx.psychedelic.count > PSYCHEDELIC_COUNT_MAX)
336 fchain->ctx.psychedelic.count = PSYCHEDELIC_COUNT_MIN;
339 CIVector *ci_vector =
340 [CIVector vectorWithX: (float)fchain->ctx.psychedelic.x
341 Y: (float)fchain->ctx.psychedelic.y];
343 [fchain->ci_filter setValue: ci_vector
344 forKey: @"inputCenter"];
345 [fchain->ci_filter setValue: [NSNumber numberWithFloat: fchain->ctx.psychedelic.count]
346 forKey: @"inputCount"];
348 @catch (NSException * e) { /* inputCenter key doesn't exist */ }
352 Filter(filter_t *filter, picture_t *src)
354 filter_sys_t *p_sys = filter->p_sys;
355 struct ci_filters_ctx *ctx = p_sys->ctx;
356 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS];
358 filter_desc_table_GetFilterTypes(p_sys->psz_filter, filter_types);
359 if (ctx->fchain->filter != filter_types[0])
362 picture_t *dst = picture_NewFromFormat(&ctx->cvpx_pool_fmt);
366 CVPixelBufferRef cvpx = cvpxpool_new_cvpx(ctx->cvpx_pool);
370 if (cvpxpic_attach(dst, cvpx, filter->vctx_out, NULL))
377 if (ctx->src_converter)
379 src = ctx->dst_converter->pf_video_filter(ctx->src_converter, src);
385 CIImage *ci_img = [CIImage imageWithCVImageBuffer: cvpxpic_get_ref(src)];
389 for (struct filter_chain *fchain = ctx->fchain;
390 fchain; fchain = fchain->next)
392 [fchain->ci_filter setValue: ci_img
393 forKey: kCIInputImageKey];
395 for (unsigned int i = 0; i < NUM_FILTER_PARAM_MAX &&
396 filter_desc_table[fchain->filter].param_descs[i].vlc; ++i)
398 NSString *ci_param_name =
399 filter_desc_table[fchain->filter].param_descs[i].ci;
400 float ci_value = atomic_load(fchain->ci_params + i);
402 [fchain->ci_filter setValue: [NSNumber numberWithFloat: ci_value]
403 forKey: ci_param_name];
406 if (filter_desc_table[fchain->filter].pf_control)
407 filter_desc_table[fchain->filter].pf_control(filter, fchain);
408 ci_img = [fchain->ci_filter valueForKey: kCIOutputImageKey];
411 [ctx->ci_ctx render: ci_img
412 toCVPixelBuffer: cvpx];
413 } /* autoreleasepool */
415 CopyInfoAndRelease(dst, src);
417 if (ctx->dst_converter)
419 dst = ctx->dst_converter->pf_video_filter(ctx->dst_converter, dst);
424 p_sys->mouse_moved = false;
429 picture_Release(dst);
430 picture_Release(src);
431 p_sys->mouse_moved = false;
436 Mouse(filter_t *filter, struct vlc_mouse_t *new,
437 const struct vlc_mouse_t *old)
439 filter_sys_t *sys = filter->p_sys;
440 sys->old_mouse = *old;
442 sys->mouse_moved = true;
447 Open_FilterInit(filter_t *filter, struct filter_chain *fchain)
449 struct filter_param_desc const *filter_param_descs =
450 filter_desc_table[fchain->filter].param_descs;
451 NSString *ci_filter_name = filter_desc_table_GetFilterName(fchain->filter);
452 if (ci_filter_name == nil)
454 char *psz_filter_name = var_InheritString(filter, "ci-filter");
456 ci_filter_name = [NSString stringWithUTF8String:psz_filter_name];
457 free(psz_filter_name);
460 fchain->ci_filter = [CIFilter filterWithName: ci_filter_name];
461 if (!fchain->ci_filter)
463 msg_Warn(filter, "filter '%s' could not be created",
464 [ci_filter_name UTF8String]);
468 for (int i = 0; i < NUM_FILTER_PARAM_MAX && filter_param_descs[i].vlc; ++i)
470 NSString *ci_param_name = filter_param_descs[i].ci;
471 char const *vlc_param_name = filter_param_descs[i].vlc;
474 if (filter_param_descs[i].vlc_type == VLC_VAR_FLOAT)
475 vlc_param_val = var_CreateGetFloatCommand(filter, vlc_param_name);
476 else if (filter_param_descs[i].vlc_type == VLC_VAR_INTEGER)
478 (float)var_CreateGetIntegerCommand(filter, vlc_param_name);
480 vlc_assert_unreachable();
482 atomic_init(fchain->ci_params + i,
483 filter_ConvertParam(vlc_param_val,
484 filter_param_descs + i));
486 var_AddCallback(filter, filter_param_descs[i].vlc,
487 ParamsCallback, fchain);
489 if (filter_desc_table[fchain->filter].pf_init)
490 filter_desc_table[fchain->filter].pf_init(filter, fchain);
496 Open_CreateFilters(filter_t *filter, struct filter_chain **p_last_filter,
497 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS])
499 struct filter_chain *new_filter;
501 for (unsigned int i = 0;
502 i < NUM_MAX_EQUIVALENT_VLC_FILTERS
503 && filter_types[i] != FILTER_NONE; ++i)
505 new_filter = filter_chain_AddFilter(p_last_filter, filter_types[i]);
508 p_last_filter = &new_filter;
509 if (Open_FilterInit(filter, new_filter) != VLC_SUCCESS)
511 for (unsigned int j = 0; j < i ; ++j)
512 filter_chain_RemoveFilter(p_last_filter, filter_types[i]);
521 cvpx_video_context_Destroy(void *priv)
523 struct ci_filters_ctx *ctx = priv;
525 if (ctx->src_converter)
527 module_unneed(ctx->src_converter, ctx->src_converter->p_module);
528 vlc_object_delete(ctx->src_converter);
530 if (ctx->dst_converter)
532 module_unneed(ctx->dst_converter, ctx->dst_converter->p_module);
533 vlc_object_delete(ctx->dst_converter);
537 CVPixelBufferPoolRelease(ctx->cvpx_pool);
541 CVPX_to_CVPX_converter_Create(filter_t *filter, bool to_rgba)
543 filter_t *converter = vlc_object_create(filter, sizeof(filter_t));
547 converter->fmt_in = filter->fmt_out;
548 converter->fmt_out = filter->fmt_out;
552 converter->fmt_out.video.i_chroma =
553 converter->fmt_out.i_codec = VLC_CODEC_CVPX_BGRA;
557 converter->fmt_in.video.i_chroma =
558 converter->fmt_in.i_codec = VLC_CODEC_CVPX_BGRA;
561 converter->p_module = module_need(converter, "video converter", NULL, false);
562 if (!converter->p_module)
564 vlc_object_delete(converter);
572 Open(vlc_object_t *obj, char const *psz_filter)
574 filter_t *filter = (filter_t *)obj;
576 switch (filter->fmt_in.video.i_chroma)
578 case VLC_CODEC_CVPX_NV12:
579 case VLC_CODEC_CVPX_UYVY:
580 case VLC_CODEC_CVPX_I420:
581 case VLC_CODEC_CVPX_BGRA:
587 if (filter->vctx_in == NULL ||
588 vlc_video_context_GetType(filter->vctx_in) != VLC_VIDEO_CONTEXT_CVPX)
591 filter_sys_t *p_sys = filter->p_sys = calloc(1, sizeof(filter_sys_t));
595 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS];
596 filter_desc_table_GetFilterTypes(psz_filter, filter_types);
598 struct ci_filters_ctx *ctx =
599 vlc_video_context_GetCVPXPrivate(filter->vctx_in, CVPX_VIDEO_CONTEXT_CIFILTERS);
602 filter->vctx_out = vlc_video_context_Hold(filter->vctx_in);
605 static const struct vlc_video_context_operations ops = {
606 cvpx_video_context_Destroy,
608 vlc_decoder_device *dec_dev =
609 filter_HoldDecoderDeviceType(filter,
610 VLC_DECODER_DEVICE_VIDEOTOOLBOX);
613 msg_Err(filter, "Missing decoder device");
617 vlc_video_context_CreateCVPX(dec_dev, CVPX_VIDEO_CONTEXT_CIFILTERS,
618 sizeof(struct ci_filters_ctx), &ops);
619 vlc_decoder_device_Release(dec_dev);
620 if (!filter->vctx_out)
623 ctx = vlc_video_context_GetCVPXPrivate(filter->vctx_out,
624 CVPX_VIDEO_CONTEXT_CIFILTERS);
627 ctx->src_converter = ctx->dst_converter = NULL;
629 ctx->cvpx_pool = nil;
630 ctx->cvpx_pool_fmt = filter->fmt_out.video;
632 if (filter->fmt_in.video.i_chroma != VLC_CODEC_CVPX_NV12
633 && filter->fmt_in.video.i_chroma != VLC_CODEC_CVPX_BGRA)
636 CVPX_to_CVPX_converter_Create(filter, true);
637 ctx->dst_converter = ctx->src_converter ?
638 CVPX_to_CVPX_converter_Create(filter, false) : NULL;
639 if (!ctx->src_converter || !ctx->dst_converter)
641 ctx->cvpx_pool_fmt.i_chroma = VLC_CODEC_CVPX_BGRA;
644 #if !TARGET_OS_IPHONE
645 CGLContextObj glctx = var_InheritAddress(filter, "macosx-glcontext");
648 msg_Err(filter, "can't find 'macosx-glcontext' var");
651 ctx->ci_ctx = [CIContext contextWithCGLContext: glctx
655 kCIContextWorkingColorSpace : [NSNull null],
656 kCIContextOutputColorSpace : [NSNull null],
659 CVEAGLContext eaglctx = var_InheritAddress(filter, "ios-eaglcontext");
662 msg_Err(filter, "can't find 'ios-eaglcontext' var");
665 ctx->ci_ctx = [CIContext contextWithEAGLContext: eaglctx];
670 ctx->cvpx_pool = cvpxpool_create(&ctx->cvpx_pool_fmt, 2);
674 if (Open_CreateFilters(filter, &ctx->fchain, filter_types))
677 p_sys->psz_filter = psz_filter;
680 filter->pf_video_filter = Filter;
681 filter->pf_video_mouse = Mouse;
686 if (filter->vctx_out)
687 vlc_video_context_Release(filter->vctx_out);
693 OpenAdjust(vlc_object_t *obj)
695 return Open(obj, "adjust");
699 OpenInvert(vlc_object_t *obj)
701 return Open(obj, "invert");
705 OpenPosterize(vlc_object_t *obj)
707 return Open(obj, "posterize");
711 OpenSepia(vlc_object_t *obj)
713 return Open(obj, "sepia");
717 OpenSharpen(vlc_object_t *obj)
719 return Open(obj, "sharpen");
723 OpenPsychedelic(vlc_object_t *obj)
725 return Open(obj, "psychedelic");
729 OpenCustom(vlc_object_t *obj)
731 return Open(obj, "custom");
735 Close(vlc_object_t *obj)
737 filter_t *filter = (filter_t *)obj;
738 filter_sys_t *p_sys = filter->p_sys;
739 struct ci_filters_ctx *ctx = p_sys->ctx;
740 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS];
742 filter_desc_table_GetFilterTypes(p_sys->psz_filter, filter_types);
743 for (unsigned int i = 0;
744 i < NUM_MAX_EQUIVALENT_VLC_FILTERS && filter_types[i] != FILTER_NONE;
746 filter_chain_RemoveFilter(&ctx->fchain, filter_types[i]);
748 vlc_video_context_Release(filter->vctx_out);
752 #define CI_CUSTOM_FILTER_TEXT N_("Use a specific Core Image Filter")
753 #define CI_CUSTOM_FILTER_LONGTEXT N_( \
754 "Example: 'CICrystallize', 'CIBumpDistortion', 'CIThermal', 'CIComicEffect'")
757 set_capability("video filter", 0)
758 set_category(CAT_VIDEO)
759 set_subcategory(SUBCAT_VIDEO_VFILTER)
760 set_description(N_("Mac OS X hardware video filters"))
763 set_callbacks(OpenAdjust, Close)
764 add_shortcut("adjust")
767 set_callbacks(OpenInvert, Close)
768 add_shortcut("invert")
771 set_callbacks(OpenPosterize, Close)
772 add_shortcut("posterize")
775 set_callbacks(OpenSepia, Close)
776 add_shortcut("sepia")
779 set_callbacks(OpenSharpen, Close)
780 add_shortcut("sharpen")
783 set_callbacks(OpenPsychedelic, Close)
784 add_shortcut("psychedelic")
787 set_callbacks(OpenCustom, Close)
789 add_string("ci-filter", "CIComicEffect", CI_CUSTOM_FILTER_TEXT, CI_CUSTOM_FILTER_LONGTEXT, true);