ci_filters: don't force BT709 colorspace
[vlc.git] / modules / video_filter / ci_filters.m
blob579ac8163027852f945c766d6cc94764a77fa822
1 /*****************************************************************************
2  * ci_filters.m: Video filters for MacOSX OpenGL video output
3  *****************************************************************************
4  * Copyright © 2017 VLC authors, VideoLAN and VideoLabs
5  *
6  * Author: Victorien Le Couviour--Tuffet <victorien.lecouviour.tuffet@gmail.com>
7  *
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.
12  *
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.
17  *
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  *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
27 #include <assert.h>
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"
37 #include "vt_utils.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"
47 enum    filter_type
49     FILTER_NONE = -1,
50     FILTER_ADJUST_HUE,
51     FILTER_ADJUST_COLOR_CONTROLS,
52     FILTER_ADJUST_GAMMA,
53     FILTER_INVERT,
54     FILTER_POSTERIZE,
55     FILTER_SEPIA,
56     FILTER_SHARPEN,
57     FILTER_PSYCHEDELIC,
58     FILTER_CUSTOM,
59     NUM_FILTERS,
60     NUM_MAX_EQUIVALENT_VLC_FILTERS = 3
63 #define NUM_FILTER_PARAM_MAX    4
65 struct  filter_chain
67     enum filter_type            filter;
68     CIFilter *                  ci_filter;
69     vlc_atomic_float            ci_params[NUM_FILTER_PARAM_MAX];
70     struct filter_chain *       next;
71     union {
72         struct
73         {
74 #define PSYCHEDELIC_COUNT_DEFAULT 5
75 #define PSYCHEDELIC_COUNT_MIN 3
76 #define PSYCHEDELIC_COUNT_MAX 40
77             int x, y;
78             unsigned count;
79         } psychedelic;
80     } ctx;
83 struct  ci_filters_ctx
85     CVPixelBufferPoolRef        cvpx_pool;
86     video_format_t              cvpx_pool_fmt;
87     CVPixelBufferPoolRef        outconv_cvpx_pool;
88     CIContext *                 ci_ctx;
89     struct filter_chain *       fchain;
90     filter_t *                  dst_converter;
93 struct filter_sys_t
95     char const *                psz_filter;
96     bool                        mouse_moved;
97     vlc_mouse_t                 old_mouse;
98     vlc_mouse_t                 mouse;
99     struct ci_filters_ctx *     ctx;
102 struct  range
104     float       min;
105     float       max;
108 struct  filter_desc
110     struct
111     {
112         char const *            vlc;
113         NSString *              ci;
114     } const             name_desc;
116     struct      filter_param_desc
117     {
118         char const *            vlc;
119         NSString *              ci;
120         struct
121         {
122             struct range    vlc;
123             struct range    ci;
124         }                       ranges[2];
125         int                     vlc_type;
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] =
137     {
138         { "adjust", @"CIHueAdjust" },
139         {
140             { "hue", @"inputAngle", {{{-180.f, +180.f}, {+3.f, -3.f}}}, VLC_VAR_FLOAT }
141         }
142     },
143     [FILTER_ADJUST_COLOR_CONTROLS] =
144     {
145         { "adjust", @"CIColorControls" },
146         {
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 }
150         }
151     },
152     [FILTER_ADJUST_GAMMA] =
153     {
154         { "adjust", @"CIGammaAdjust" },
155         {
156             { "gamma", @"inputPower", {{{.01f, 1.f}, {10.f, 1.f}}, {{1.f, 10.f}, {1.f, .01f}}}, VLC_VAR_FLOAT }
157         }
158     },
159     [FILTER_INVERT] =
160     {
161         { "invert", @"CIColorInvert" }
162     },
163     [FILTER_POSTERIZE] =
164     {
165         { "posterize", @"CIColorPosterize" },
166         {
167             { "posterize-level", @"inputLevels", {{{2.f, 256.f}, {2.f, 256.f}}}, VLC_VAR_INTEGER }
168         }
169     },
170     [FILTER_SEPIA] =
171     {
172         { "sepia", @"CISepiaTone" },
173         {
174             { "sepia-intensity", @"inputIntensity", {{{.0f, 255.f}, {.0f, 1.f}}}, VLC_VAR_INTEGER }
175         }
176     },
177     [FILTER_SHARPEN] =
178     {
179         { "sharpen", @"CISharpenLuminance" },
180         {
181             { "sharpen-sigma", @"inputSharpness", {{{.0f, 2.f}, {.0f, 5.f}}}, VLC_VAR_FLOAT }
182         }
183     },
184     [FILTER_PSYCHEDELIC] =
185     {
186         { "psychedelic", @"CIKaleidoscope" }, { { } },
187         filter_PsychedelicInit,
188         filter_PsychedelicControl
189     },
190     [FILTER_CUSTOM] =
191     {
192         { "custom" }, { { } },
193         filter_PsychedelicInit,
194         filter_PsychedelicControl
195     },
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));
206     if (!elem)
207         return NULL;
208     elem->filter = filter;
210     if (!*fchain)
211         *fchain = elem;
212     else
213     {
214         struct filter_chain *it = *fchain;
215         while (it->next) it = it->next;
216         it->next = elem;
217     }
219     return elem;
222 static void
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)
231         prev = to_del;
232     assert(to_del);
233     if (!prev)
234         *fchain = to_del->next;
235     else
236         prev->next = to_del->next;
238     free(to_del);
241 static void
242 filter_desc_table_GetFilterTypes
243 (char const *vlc_filter_name,
244  enum filter_type filter_types[NUM_MAX_EQUIVALENT_VLC_FILTERS])
246     int j = 0;
247     for (int i = 0; i < NUM_FILTERS; ++i)
248         if (!strcmp(filter_desc_table[i].name_desc.vlc, vlc_filter_name))
249         {
250             assert(j < NUM_MAX_EQUIVALENT_VLC_FILTERS);
251             filter_types[j++] = i;
252         }
253     assert(j);
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;
264 static float
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)
278             break;
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);
286 static int
287 ParamsCallback(vlc_object_t *obj,
288                char const *psz_var,
289                vlc_value_t oldval, vlc_value_t newval,
290                void *p_data)
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;
297     unsigned int i = 0;
298     while (i < NUM_FILTER_PARAM_MAX &&
299            strcmp(filter_param_descs[i].vlc, psz_var))
300         ++i;
301     assert(i < NUM_FILTER_PARAM_MAX);
303     float new_vlc_val;
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;
308     else
309         vlc_assert_unreachable();
311     vlc_atomic_store_float(filter->ci_params + i,
312                            filter_ConvertParam(new_vlc_val,
313                                                filter_param_descs + i));
315     return VLC_SUCCESS;
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)
331     {
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)
337         {
338             fchain->ctx.psychedelic.count++;
339             if (fchain->ctx.psychedelic.count > PSYCHEDELIC_COUNT_MAX)
340                 fchain->ctx.psychedelic.count = PSYCHEDELIC_COUNT_MIN;
341         }
342     }
343     CIVector *ci_vector =
344         [CIVector vectorWithX: (float)fchain->ctx.psychedelic.x
345                             Y: (float)fchain->ctx.psychedelic.y];
346     @try {
347         [fchain->ci_filter setValue: ci_vector
348                              forKey: @"inputCenter"];
349         [fchain->ci_filter setValue: [NSNumber numberWithFloat: fchain->ctx.psychedelic.count]
350                              forKey: @"inputCount"];
351     }
352     @catch (NSException * e) { /* inputCenter key doesn't exist */ }
355 static picture_t *
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])
363         return src;
365     picture_t *dst = picture_NewFromFormat(&ctx->cvpx_pool_fmt);
366     if (!dst)
367         goto error;
369     CVPixelBufferRef cvpx = cvpxpool_new_cvpx(ctx->cvpx_pool);
370     if (!cvpx)
371         goto error;
373     if (cvpxpic_attach(dst, cvpx))
374     {
375         CFRelease(cvpx);
376         goto error;
377     }
378     CFRelease(cvpx);
380     @autoreleasepool {
381         CIImage *ci_img = [CIImage imageWithCVImageBuffer: cvpxpic_get_ref(src)];
382         if (!ci_img)
383             goto error;
385         for (struct filter_chain *fchain = ctx->fchain;
386              fchain; fchain = fchain->next)
387         {
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)
393             {
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];
400             }
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];
405         }
407         [ctx->ci_ctx render: ci_img
408             toCVPixelBuffer: cvpx];
409     } /* autoreleasepool */
411     CopyInfoAndRelease(dst, src);
413     if (ctx->dst_converter)
414     {
415         dst = ctx->dst_converter->pf_video_filter(ctx->dst_converter, dst);
416         if (!dst)
417             return NULL;
418     }
420     filter->p_sys->mouse_moved = false;
421     return dst;
423 error:
424     if (dst)
425         picture_Release(dst);
426     picture_Release(src);
427     filter->p_sys->mouse_moved = false;
428     return NULL;
431 static int
432 Mouse(filter_t *filter, struct vlc_mouse_t *mouse,
433       const struct vlc_mouse_t *old, const struct vlc_mouse_t *new)
435     VLC_UNUSED(mouse);
436     filter_sys_t *sys = filter->p_sys;
437     sys->old_mouse = *old;
438     sys->mouse = *new;
439     sys->mouse_moved = true;
440     *mouse = *new;
441     return VLC_SUCCESS;
444 static int
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)
451     {
452         char *psz_filter_name = var_InheritString(filter, "ci-filter");
453         if (psz_filter_name)
454             ci_filter_name = [NSString stringWithUTF8String:psz_filter_name];
455         free(psz_filter_name);
456     }
458     fchain->ci_filter = [CIFilter filterWithName: ci_filter_name];
459     if (!fchain->ci_filter)
460     {
461         msg_Warn(filter, "filter '%s' could not be created",
462                  [ci_filter_name UTF8String]);
463         return VLC_EGENERIC;
464     }
466     for (int i = 0; i < NUM_FILTER_PARAM_MAX && filter_param_descs[i].vlc; ++i)
467     {
468         NSString *ci_param_name = filter_param_descs[i].ci;
469         char const *vlc_param_name = filter_param_descs[i].vlc;
471         float vlc_param_val;
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)
475             vlc_param_val =
476                 (float)var_CreateGetIntegerCommand(filter, vlc_param_name);
477         else
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);
486     }
487     if (filter_desc_table[fchain->filter].pf_init)
488         filter_desc_table[fchain->filter].pf_init(filter, fchain);
490     return VLC_SUCCESS;
493 static int
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)
502     {
503         new_filter = filter_chain_AddFilter(p_last_filter, filter_types[i]);
504         if (!new_filter)
505             return VLC_EGENERIC;
506         p_last_filter = &new_filter;
507         if (Open_FilterInit(filter, new_filter) != VLC_SUCCESS)
508         {
509             for (unsigned int j = 0; j < i ; ++j)
510                 filter_chain_RemoveFilter(p_last_filter, filter_types[i]);
511             return VLC_EGENERIC;
512         }
513     }
515     return VLC_SUCCESS;
518 static picture_t *
519 CVPX_buffer_new(filter_t *converter)
521     CVPixelBufferPoolRef pool = converter->owner.sys;
522     CVPixelBufferRef cvpx = cvpxpool_new_cvpx(pool);
523     if (!cvpx)
524         return NULL;
526     picture_t *pic = picture_NewFromFormat(&converter->fmt_out.video);
527     if (!pic || cvpxpic_attach(pic, cvpx))
528     {
529         CFRelease(cvpx);
530         return NULL;
531     }
532     CFRelease(cvpx);
534     return pic;
537 static void
538 Close_RemoveConverters(filter_t *filter, struct ci_filters_ctx *ctx)
540     VLC_UNUSED(filter);
541     if (ctx->dst_converter)
542     {
543         module_unneed(ctx->dst_converter, ctx->dst_converter->p_module);
544         vlc_object_release(ctx->dst_converter);
545         CVPixelBufferPoolRelease(ctx->outconv_cvpx_pool);
546     }
549 static int
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);
555     if (!ctx->cvpx_pool)
556         goto error;
558     ctx->dst_converter = vlc_object_create(filter, sizeof(filter_t));
559     if (!ctx->dst_converter)
560         goto error;
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)
570         goto error;
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)
578         goto error;
580     return VLC_SUCCESS;
582 error:
583     if (ctx->dst_converter)
584     {
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);
590     }
591     return VLC_EGENERIC;
594 static int
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)
600     {
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)
606             {
607                 msg_Warn(obj, "iOS/macOS version is too old, aborting...");
608                 return VLC_EGENERIC;
609             }
610             break;
611         default:
612             return VLC_EGENERIC;
613     }
615     filter->p_sys = calloc(1, sizeof(filter_sys_t));
616     if (!filter->p_sys)
617         return VLC_ENOMEM;
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");
623     if (!ctx)
624     {
625         ctx = calloc(1, sizeof(*ctx));
626         if (!ctx)
627             goto error;
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))
632             goto error;
634 #if !TARGET_OS_IPHONE
635         CGLContextObj glctx = var_InheritAddress(filter, "macosx-glcontext");
636         if (!glctx)
637         {
638             msg_Err(filter, "can't find 'macosx-glcontext' var");
639             goto error;
640         }
641         ctx->ci_ctx = [CIContext contextWithCGLContext: glctx
642                                            pixelFormat: nil
643                                             colorSpace: nil
644                                                options: nil];
645 #else
646         CVEAGLContext eaglctx = var_InheritAddress(filter, "ios-eaglcontext");
647         if (!eaglctx)
648         {
649             msg_Err(filter, "can't find 'ios-eaglcontext' var");
650             goto error;
651         }
652         ctx->ci_ctx = [CIContext contextWithEAGLContext: eaglctx];
653 #endif
654         if (!ctx->ci_ctx)
655             goto error;
657         if (!ctx->cvpx_pool)
658         {
659             ctx->cvpx_pool_fmt = filter->fmt_out.video;
660             ctx->cvpx_pool = cvpxpool_create(&ctx->cvpx_pool_fmt, 2);
661             if (!ctx->cvpx_pool)
662                 goto error;
663         }
665         if (Open_CreateFilters(filter, &ctx->fchain, filter_types))
666             goto error;
668         var_Create(filter->obj.parent, "ci-filters-ctx", VLC_VAR_ADDRESS);
669         var_SetAddress(filter->obj.parent, "ci-filters-ctx", ctx);
670     }
671     else if (Open_CreateFilters(filter, &ctx->fchain, filter_types))
672         goto error;
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;
680     return VLC_SUCCESS;
682 error:
683     if (ctx)
684     {
685         Close_RemoveConverters(filter, ctx);
686         if (ctx->cvpx_pool)
687             CVPixelBufferPoolRelease(ctx->cvpx_pool);
688         free(ctx);
689     }
690     free(filter->p_sys);
691     return VLC_EGENERIC;
694 static int
695 OpenAdjust(vlc_object_t *obj)
697     return Open(obj, "adjust");
700 static int
701 OpenInvert(vlc_object_t *obj)
703     return Open(obj, "invert");
706 static int
707 OpenPosterize(vlc_object_t *obj)
709     return Open(obj, "posterize");
712 static int
713 OpenSepia(vlc_object_t *obj)
715     return Open(obj, "sepia");
718 static int
719 OpenSharpen(vlc_object_t *obj)
721     return Open(obj, "sharpen");
724 static int
725 OpenPsychedelic(vlc_object_t *obj)
727     return Open(obj, "psychedelic");
730 static int
731 OpenCustom(vlc_object_t *obj)
733     return Open(obj, "custom");
736 static void
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;
746          ++i)
747         filter_chain_RemoveFilter(&ctx->fchain, filter_types[i]);
749     if (!ctx->fchain)
750     {
751         Close_RemoveConverters(filter, ctx);
752         if (ctx->cvpx_pool)
753             CVPixelBufferPoolRelease(ctx->cvpx_pool);
754         free(ctx);
755         var_Destroy(filter->obj.parent, "ci-filters-ctx");
756     }
757     free(filter->p_sys);
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'")
764 vlc_module_begin()
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"))
770     add_submodule()
771     set_callbacks(OpenAdjust, Close)
772     add_shortcut("adjust")
774     add_submodule()
775     set_callbacks(OpenInvert, Close)
776     add_shortcut("invert")
778     add_submodule()
779     set_callbacks(OpenPosterize, Close)
780     add_shortcut("posterize")
782     add_submodule()
783     set_callbacks(OpenSepia, Close)
784     add_shortcut("sepia")
786     add_submodule()
787     set_callbacks(OpenSharpen, Close)
788     add_shortcut("sharpen")
790     add_submodule()
791     set_callbacks(OpenPsychedelic, Close)
792     add_shortcut("psychedelic")
794     add_submodule()
795     set_callbacks(OpenCustom, Close)
796     add_shortcut("ci")
797     add_string("ci-filter", "CIComicEffect", CI_CUSTOM_FILTER_TEXT, CI_CUSTOM_FILTER_LONGTEXT, true);
798 vlc_module_end()
800 #pragma clang diagnostic pop