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 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_atomic.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>
44 #pragma clang diagnostic push
45 #pragma clang diagnostic ignored "-Wpartial-availability"
51 FILTER_ADJUST_COLOR_CONTROLS,
60 NUM_MAX_EQUIVALENT_VLC_FILTERS = 3
63 #define NUM_FILTER_PARAM_MAX 4
67 enum filter_type filter;
69 vlc_atomic_float ci_params[NUM_FILTER_PARAM_MAX];
70 struct filter_chain * next;
74 #define PSYCHEDELIC_COUNT_DEFAULT 5
75 #define PSYCHEDELIC_COUNT_MIN 3
76 #define PSYCHEDELIC_COUNT_MAX 40
85 CVPixelBufferPoolRef cvpx_pool;
86 video_format_t cvpx_pool_fmt;
87 CVPixelBufferPoolRef outconv_cvpx_pool;
89 struct filter_chain * fchain;
90 filter_t * dst_converter;
95 char const * psz_filter;
97 vlc_mouse_t old_mouse;
99 struct ci_filters_ctx * ctx;
116 struct filter_param_desc
126 } const param_descs[NUM_FILTER_PARAM_MAX];
127 void (*pf_init)(filter_t *filter, struct filter_chain *fchain);
128 void (*pf_control)(filter_t *filter, struct filter_chain *fchain);
131 static void filter_PsychedelicInit(filter_t *filter, struct filter_chain *fchain);
132 static void filter_PsychedelicControl(filter_t *filter, struct filter_chain *fchain);
134 static struct filter_desc filter_desc_table[] =
136 [FILTER_ADJUST_HUE] =
138 { "adjust", @"CIHueAdjust" },
140 { "hue", @"inputAngle", {{{-180.f, +180.f}, {+3.f, -3.f}}}, VLC_VAR_FLOAT }
143 [FILTER_ADJUST_COLOR_CONTROLS] =
145 { "adjust", @"CIColorControls" },
147 { "contrast", @"inputContrast", {{{.0f, 2.f}, {.0f, 2.f}}}, VLC_VAR_FLOAT },
148 { "brightness", @"inputBrightness", {{{.0f, 2.f}, {-1.f, +1.f}}}, VLC_VAR_FLOAT },
149 { "saturation", @"inputSaturation", {{{.0f, 3.f}, {.0f, 2.7f}}}, VLC_VAR_FLOAT }
152 [FILTER_ADJUST_GAMMA] =
154 { "adjust", @"CIGammaAdjust" },
156 { "gamma", @"inputPower", {{{.01f, 1.f}, {10.f, 1.f}}, {{1.f, 10.f}, {1.f, .01f}}}, VLC_VAR_FLOAT }
161 { "invert", @"CIColorInvert" }
165 { "posterize", @"CIColorPosterize" },
167 { "posterize-level", @"inputLevels", {{{2.f, 256.f}, {2.f, 256.f}}}, VLC_VAR_INTEGER }
172 { "sepia", @"CISepiaTone" },
174 { "sepia-intensity", @"inputIntensity", {{{.0f, 255.f}, {.0f, 1.f}}}, VLC_VAR_INTEGER }
179 { "sharpen", @"CISharpenLuminance" },
181 { "sharpen-sigma", @"inputSharpness", {{{.0f, 2.f}, {.0f, 5.f}}}, VLC_VAR_FLOAT }
184 [FILTER_PSYCHEDELIC] =
186 { "psychedelic", @"CIKaleidoscope" }, { { } },
187 filter_PsychedelicInit,
188 filter_PsychedelicControl
192 { "custom" }, { { } },
193 filter_PsychedelicInit,
194 filter_PsychedelicControl
198 #define GET_CI_VALUE(vlc_value, vlc_range, ci_range) \
199 ((vlc_value - vlc_range.min) * (ci_range.max - ci_range.min) / \
200 (vlc_range.max - vlc_range.min) + ci_range.min)
202 static struct filter_chain *
203 filter_chain_AddFilter(struct filter_chain **fchain, enum filter_type filter)
205 struct filter_chain *elem = calloc(1, sizeof(*elem));
208 elem->filter = filter;
214 struct filter_chain *it = *fchain;
215 while (it->next) it = it->next;
223 filter_chain_RemoveFilter(struct filter_chain **fchain,
224 enum filter_type filter)
226 struct filter_chain *prev = NULL;
227 struct filter_chain *to_del;
229 for (to_del = *fchain; to_del && to_del->filter != filter;
230 to_del = to_del->next)
234 *fchain = to_del->next;
236 prev->next = to_del->next;
242 filter_desc_table_GetFilterTypes
243 (char const *vlc_filter_name,
244 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS])
247 for (int i = 0; i < NUM_FILTERS; ++i)
248 if (!strcmp(filter_desc_table[i].name_desc.vlc, vlc_filter_name))
250 assert(j < NUM_MAX_EQUIVALENT_VLC_FILTERS);
251 filter_types[j++] = i;
254 while (j < NUM_MAX_EQUIVALENT_VLC_FILTERS)
255 filter_types[j++] = FILTER_NONE;
258 static inline NSString *
259 filter_desc_table_GetFilterName(enum filter_type type)
260 { assert(type < NUM_FILTERS);
261 return filter_desc_table[type].name_desc.ci;
265 filter_ConvertParam(float f_vlc_val,
266 struct filter_param_desc const *param_desc)
268 struct range clip_range = { param_desc->ranges[0].vlc.min,
269 param_desc->ranges[1].vlc.max
270 ? param_desc->ranges[1].vlc.max
271 : param_desc->ranges[0].vlc.max };
272 f_vlc_val = VLC_CLIP(f_vlc_val, clip_range.min, clip_range.max);
274 unsigned int range_idx;
275 for (range_idx = 0; range_idx < 2; ++range_idx)
276 if (f_vlc_val >= param_desc->ranges[range_idx].vlc.min &&
277 f_vlc_val <= param_desc->ranges[range_idx].vlc.max)
279 assert(range_idx < 2);
281 return GET_CI_VALUE(f_vlc_val,
282 param_desc->ranges[range_idx].vlc,
283 param_desc->ranges[range_idx].ci);
287 ParamsCallback(vlc_object_t *obj,
289 vlc_value_t oldval, vlc_value_t newval,
292 VLC_UNUSED(obj); VLC_UNUSED(oldval);
293 struct filter_chain *filter = p_data;
294 struct filter_param_desc const *filter_param_descs =
295 filter_desc_table[filter->filter].param_descs;
298 while (i < NUM_FILTER_PARAM_MAX &&
299 strcmp(filter_param_descs[i].vlc, psz_var))
301 assert(i < NUM_FILTER_PARAM_MAX);
304 if (filter_param_descs[i].vlc_type == VLC_VAR_FLOAT)
305 new_vlc_val = newval.f_float;
306 else if (filter_param_descs[i].vlc_type == VLC_VAR_INTEGER)
307 new_vlc_val = newval.i_int;
309 vlc_assert_unreachable();
311 vlc_atomic_store_float(filter->ci_params + i,
312 filter_ConvertParam(new_vlc_val,
313 filter_param_descs + i));
318 static void filter_PsychedelicInit(filter_t *filter, struct filter_chain *fchain)
320 filter_sys_t *sys = filter->p_sys;
321 fchain->ctx.psychedelic.x = filter->fmt_in.video.i_width / 2;
322 fchain->ctx.psychedelic.y = filter->fmt_in.video.i_height / 2;
323 fchain->ctx.psychedelic.count = PSYCHEDELIC_COUNT_DEFAULT;
326 static void filter_PsychedelicControl(filter_t *filter, struct filter_chain *fchain)
328 filter_sys_t *sys = filter->p_sys;
330 if (sys->mouse_moved)
332 fchain->ctx.psychedelic.x = sys->mouse.i_x;
333 fchain->ctx.psychedelic.y = filter->fmt_in.video.i_height
334 - sys->mouse.i_y - 1;
336 if (sys->mouse.i_pressed)
338 fchain->ctx.psychedelic.count++;
339 if (fchain->ctx.psychedelic.count > PSYCHEDELIC_COUNT_MAX)
340 fchain->ctx.psychedelic.count = PSYCHEDELIC_COUNT_MIN;
343 CIVector *ci_vector =
344 [CIVector vectorWithX: (float)fchain->ctx.psychedelic.x
345 Y: (float)fchain->ctx.psychedelic.y];
347 [fchain->ci_filter setValue: ci_vector
348 forKey: @"inputCenter"];
349 [fchain->ci_filter setValue: [NSNumber numberWithFloat: fchain->ctx.psychedelic.count]
350 forKey: @"inputCount"];
352 @catch (NSException * e) { /* inputCenter key doesn't exist */ }
356 Filter(filter_t *filter, picture_t *src)
358 struct ci_filters_ctx *ctx = filter->p_sys->ctx;
359 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS];
361 filter_desc_table_GetFilterTypes(filter->p_sys->psz_filter, filter_types);
362 if (ctx->fchain->filter != filter_types[0])
365 picture_t *dst = picture_NewFromFormat(&ctx->cvpx_pool_fmt);
369 CVPixelBufferRef cvpx = cvpxpool_new_cvpx(ctx->cvpx_pool);
373 if (cvpxpic_attach(dst, cvpx))
381 CIImage *ci_img = [CIImage imageWithCVImageBuffer: cvpxpic_get_ref(src)];
385 for (struct filter_chain *fchain = ctx->fchain;
386 fchain; fchain = fchain->next)
388 [fchain->ci_filter setValue: ci_img
389 forKey: kCIInputImageKey];
391 for (unsigned int i = 0; i < NUM_FILTER_PARAM_MAX &&
392 filter_desc_table[fchain->filter].param_descs[i].vlc; ++i)
394 NSString *ci_param_name =
395 filter_desc_table[fchain->filter].param_descs[i].ci;
396 float ci_value = vlc_atomic_load_float(fchain->ci_params + i);
398 [fchain->ci_filter setValue: [NSNumber numberWithFloat: ci_value]
399 forKey: ci_param_name];
402 if (filter_desc_table[fchain->filter].pf_control)
403 filter_desc_table[fchain->filter].pf_control(filter, fchain);
404 ci_img = [fchain->ci_filter valueForKey: kCIOutputImageKey];
407 [ctx->ci_ctx render: ci_img
408 toCVPixelBuffer: cvpx];
409 } /* autoreleasepool */
411 CopyInfoAndRelease(dst, src);
413 if (ctx->dst_converter)
415 dst = ctx->dst_converter->pf_video_filter(ctx->dst_converter, dst);
420 filter->p_sys->mouse_moved = false;
425 picture_Release(dst);
426 picture_Release(src);
427 filter->p_sys->mouse_moved = false;
432 Mouse(filter_t *filter, struct vlc_mouse_t *mouse,
433 const struct vlc_mouse_t *old, const struct vlc_mouse_t *new)
436 filter_sys_t *sys = filter->p_sys;
437 sys->old_mouse = *old;
439 sys->mouse_moved = true;
445 Open_FilterInit(filter_t *filter, struct filter_chain *fchain)
447 struct filter_param_desc const *filter_param_descs =
448 filter_desc_table[fchain->filter].param_descs;
449 NSString *ci_filter_name = filter_desc_table_GetFilterName(fchain->filter);
450 if (ci_filter_name == nil)
452 char *psz_filter_name = var_InheritString(filter, "ci-filter");
454 ci_filter_name = [NSString stringWithUTF8String:psz_filter_name];
455 free(psz_filter_name);
458 fchain->ci_filter = [CIFilter filterWithName: ci_filter_name];
459 if (!fchain->ci_filter)
461 msg_Warn(filter, "filter '%s' could not be created",
462 [ci_filter_name UTF8String]);
466 for (int i = 0; i < NUM_FILTER_PARAM_MAX && filter_param_descs[i].vlc; ++i)
468 NSString *ci_param_name = filter_param_descs[i].ci;
469 char const *vlc_param_name = filter_param_descs[i].vlc;
472 if (filter_param_descs[i].vlc_type == VLC_VAR_FLOAT)
473 vlc_param_val = var_CreateGetFloatCommand(filter, vlc_param_name);
474 else if (filter_param_descs[i].vlc_type == VLC_VAR_INTEGER)
476 (float)var_CreateGetIntegerCommand(filter, vlc_param_name);
478 vlc_assert_unreachable();
480 vlc_atomic_init_float(fchain->ci_params + i,
481 filter_ConvertParam(vlc_param_val,
482 filter_param_descs + i));
484 var_AddCallback(filter, filter_param_descs[i].vlc,
485 ParamsCallback, fchain);
487 if (filter_desc_table[fchain->filter].pf_init)
488 filter_desc_table[fchain->filter].pf_init(filter, fchain);
494 Open_CreateFilters(filter_t *filter, struct filter_chain **p_last_filter,
495 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS])
497 struct filter_chain *new_filter;
499 for (unsigned int i = 0;
500 i < NUM_MAX_EQUIVALENT_VLC_FILTERS
501 && filter_types[i] != FILTER_NONE; ++i)
503 new_filter = filter_chain_AddFilter(p_last_filter, filter_types[i]);
506 p_last_filter = &new_filter;
507 if (Open_FilterInit(filter, new_filter) != VLC_SUCCESS)
509 for (unsigned int j = 0; j < i ; ++j)
510 filter_chain_RemoveFilter(p_last_filter, filter_types[i]);
519 CVPX_buffer_new(filter_t *converter)
521 CVPixelBufferPoolRef pool = converter->owner.sys;
522 CVPixelBufferRef cvpx = cvpxpool_new_cvpx(pool);
526 picture_t *pic = picture_NewFromFormat(&converter->fmt_out.video);
527 if (!pic || cvpxpic_attach(pic, cvpx))
538 Close_RemoveConverters(filter_t *filter, struct ci_filters_ctx *ctx)
541 if (ctx->dst_converter)
543 module_unneed(ctx->dst_converter, ctx->dst_converter->p_module);
544 vlc_object_release(ctx->dst_converter);
545 CVPixelBufferPoolRelease(ctx->outconv_cvpx_pool);
550 Open_AddConverter(filter_t *filter, struct ci_filters_ctx *ctx)
552 ctx->cvpx_pool_fmt = filter->fmt_in.video;
553 ctx->cvpx_pool_fmt.i_chroma = VLC_CODEC_CVPX_BGRA;
554 ctx->cvpx_pool = cvpxpool_create(&ctx->cvpx_pool_fmt, 3);
558 ctx->dst_converter = vlc_object_create(filter, sizeof(filter_t));
559 if (!ctx->dst_converter)
562 ctx->dst_converter->fmt_in = filter->fmt_out;
563 ctx->dst_converter->fmt_out = filter->fmt_out;
564 ctx->dst_converter->fmt_in.video.i_chroma =
565 ctx->dst_converter->fmt_in.i_codec = VLC_CODEC_CVPX_BGRA;
567 ctx->outconv_cvpx_pool =
568 cvpxpool_create(&filter->fmt_out.video, 2);
569 if (!ctx->outconv_cvpx_pool)
572 ctx->dst_converter->owner.sys = ctx->outconv_cvpx_pool;
573 ctx->dst_converter->owner.video.buffer_new = CVPX_buffer_new;
575 ctx->dst_converter->p_module =
576 module_need(ctx->dst_converter, "video converter", NULL, false);
577 if (!ctx->dst_converter->p_module)
583 if (ctx->dst_converter)
585 if (ctx->dst_converter->p_module)
586 module_unneed(ctx->dst_converter, ctx->dst_converter->p_module);
587 vlc_object_release(ctx->dst_converter);
588 if (ctx->outconv_cvpx_pool)
589 CVPixelBufferPoolRelease(ctx->outconv_cvpx_pool);
595 Open(vlc_object_t *obj, char const *psz_filter)
597 filter_t *filter = (filter_t *)obj;
599 switch (filter->fmt_in.video.i_chroma)
601 case VLC_CODEC_CVPX_NV12:
602 case VLC_CODEC_CVPX_UYVY:
603 case VLC_CODEC_CVPX_I420:
604 case VLC_CODEC_CVPX_BGRA:
605 if (&kCGColorSpaceITUR_709 == nil)
607 msg_Warn(obj, "iOS/macOS version is too old, aborting...");
615 filter->p_sys = calloc(1, sizeof(filter_sys_t));
619 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS];
620 filter_desc_table_GetFilterTypes(psz_filter, filter_types);
622 struct ci_filters_ctx *ctx = var_InheritAddress(filter, "ci-filters-ctx");
625 ctx = calloc(1, sizeof(*ctx));
629 if (filter->fmt_in.video.i_chroma != VLC_CODEC_CVPX_NV12
630 && filter->fmt_in.video.i_chroma != VLC_CODEC_CVPX_BGRA
631 && Open_AddConverter(filter, ctx))
634 #if !TARGET_OS_IPHONE
635 CGLContextObj glctx = var_InheritAddress(filter, "macosx-glcontext");
638 msg_Err(filter, "can't find 'macosx-glcontext' var");
641 ctx->ci_ctx = [CIContext contextWithCGLContext: glctx
646 CVEAGLContext eaglctx = var_InheritAddress(filter, "ios-eaglcontext");
649 msg_Err(filter, "can't find 'ios-eaglcontext' var");
652 ctx->ci_ctx = [CIContext contextWithEAGLContext: eaglctx];
659 ctx->cvpx_pool_fmt = filter->fmt_out.video;
660 ctx->cvpx_pool = cvpxpool_create(&ctx->cvpx_pool_fmt, 2);
665 if (Open_CreateFilters(filter, &ctx->fchain, filter_types))
668 var_Create(filter->obj.parent, "ci-filters-ctx", VLC_VAR_ADDRESS);
669 var_SetAddress(filter->obj.parent, "ci-filters-ctx", ctx);
671 else if (Open_CreateFilters(filter, &ctx->fchain, filter_types))
674 filter->p_sys->psz_filter = psz_filter;
675 filter->p_sys->ctx = ctx;
677 filter->pf_video_filter = Filter;
678 filter->pf_video_mouse = Mouse;
685 Close_RemoveConverters(filter, ctx);
687 CVPixelBufferPoolRelease(ctx->cvpx_pool);
695 OpenAdjust(vlc_object_t *obj)
697 return Open(obj, "adjust");
701 OpenInvert(vlc_object_t *obj)
703 return Open(obj, "invert");
707 OpenPosterize(vlc_object_t *obj)
709 return Open(obj, "posterize");
713 OpenSepia(vlc_object_t *obj)
715 return Open(obj, "sepia");
719 OpenSharpen(vlc_object_t *obj)
721 return Open(obj, "sharpen");
725 OpenPsychedelic(vlc_object_t *obj)
727 return Open(obj, "psychedelic");
731 OpenCustom(vlc_object_t *obj)
733 return Open(obj, "custom");
737 Close(vlc_object_t *obj)
739 filter_t *filter = (filter_t *)obj;
740 struct ci_filters_ctx *ctx = filter->p_sys->ctx;
741 enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS];
743 filter_desc_table_GetFilterTypes(filter->p_sys->psz_filter, filter_types);
744 for (unsigned int i = 0;
745 i < NUM_MAX_EQUIVALENT_VLC_FILTERS && filter_types[i] != FILTER_NONE;
747 filter_chain_RemoveFilter(&ctx->fchain, filter_types[i]);
751 Close_RemoveConverters(filter, ctx);
753 CVPixelBufferPoolRelease(ctx->cvpx_pool);
755 var_Destroy(filter->obj.parent, "ci-filters-ctx");
760 #define CI_CUSTOM_FILTER_TEXT N_("Use a specific Core Image Filter")
761 #define CI_CUSTOM_FILTER_LONGTEXT N_( \
762 "Example: 'CICrystallize', 'CIBumpDistortion', 'CIThermal', 'CIComicEffect'")
765 set_capability("video filter", 0)
766 set_category(CAT_VIDEO)
767 set_subcategory(SUBCAT_VIDEO_VFILTER)
768 set_description(N_("Mac OS X hardware video filters"))
771 set_callbacks(OpenAdjust, Close)
772 add_shortcut("adjust")
775 set_callbacks(OpenInvert, Close)
776 add_shortcut("invert")
779 set_callbacks(OpenPosterize, Close)
780 add_shortcut("posterize")
783 set_callbacks(OpenSepia, Close)
784 add_shortcut("sepia")
787 set_callbacks(OpenSharpen, Close)
788 add_shortcut("sharpen")
791 set_callbacks(OpenPsychedelic, Close)
792 add_shortcut("psychedelic")
795 set_callbacks(OpenCustom, Close)
797 add_string("ci-filter", "CIComicEffect", CI_CUSTOM_FILTER_TEXT, CI_CUSTOM_FILTER_LONGTEXT, true);
800 #pragma clang diagnostic pop